反应式聚合最初没有错误地发布到客户端。当Meteor.user
从客户端更新Meteor.call()
集合时,似乎触发了该错误:
updateProductFavorites = (product_key, action) => {
const { ranking } = this.props
const { product_keys } = ranking[0]
Meteor.call('Accounts.updateProductFavorites', product_key, action, (err, response) => {
if (err)
makeAlert(err.reason, 'danger', 3000)
else
this.getProductsByKeys(product_keys)
})
}
我已经订阅了Meteor.user()
和反应式聚合:
export default withTracker(() => {
const handle = Meteor.subscribe("products.RankingList")
return {
ranking: AggregatedProductRanking.find({}).fetch(),
user: Meteor.user(),
isLoading: !handle.ready() || !Meteor.user()
}
})(ProductRankingList)
我已经在两侧声明并导入了clientCollection
,正如本answer中所建议的那样。这是服务器端的相关代码:
const getProductRankingList = (context) => ReactiveAggregate(context, Meteor.users, [
// aggregation stages can be seen in the code snippet below
], { clientCollection: "aggregatedProductRanking"})
Meteor.methods({
'Accounts.updateProductFavorites': function(product_key, action) {
allowOrDeny(this.userId)
action = action == 'add' ? { $addToSet: { productFavorites: product_key }} : { $pull: { productFavorites: product_key }}
return Meteor.users.update({_id: this.userId}, action)
}
})
Meteor.publish('products.RankingList', function() {
const callback = () => this.stop()
allowOrDenySubscription(this.userId, callback)
return getProductRankingList(this)
})
让我感到困惑的是,即使抛出了此错误,update
调用的Meteor.call('Accounts.updateProductFavorites')
仍然可以可靠地执行。
因此,对登录的Meteor.user()
所做的更改将流回客户端,并且组件将重新提供。只有ReactiveAggregate的订阅似乎停止工作。引发以下错误,我必须重新加载浏览器才能查看聚合结果中的更改。 (底部有完整的堆栈跟踪)
Uncaught Error: Expected to find a document to change
at Object.update (collection.js:207)
at Object.store.<computed> [as update] (livedata_connection.js:310)
...
// In a certain case the error message is a bit different:
Exception in flushing DDP buffered writes: Error: Expected to find a document to change
at Object.update (collection.js:207)
at Object.store.<computed> [as update] (livedata_connection.js:310)
...
我猜测update()
会调用此ReactiveAggregate()
,以便填充clientCollection
。 但是我在做什么错了?
有关更完整的代码示例:
import { Meteor } from 'meteor/meteor'
import { ReactiveAggregate } from 'meteor/jcbernack:reactive-aggregate';
// Collections
import { AggregatedProductRanking } from '../imports/collections'
const getProductRankingList = (context) => ReactiveAggregate(context, Meteor.users, [
{
$match: { productFavorites: {$ne: [] }}
},{
$project: {
_id: 0,
productFavorites: { $concatArrays: "$productFavorites" },
}
},{
$unwind: "$productFavorites"
},{
$facet: {
rankingList: [
{
$group: {
_id: "$productFavorites",
count: { $sum: 1 }
}
},{
$sort: { "count": -1 }
}
],
product_keys: [
{
$group: {
_id: 0,
product_keys: { $addToSet: "$productFavorites" }
}
}
]
}
},{
$unwind: "$product_keys"
},{
$project: {
_id: 0,
rankingList: 1,
product_keys: "$product_keys.product_keys"
}
}
], { clientCollection: "aggregatedProductRanking"})
Meteor.methods({
'Accounts.updateProductFavorites': function(product_key, action) {
allowOrDeny(this.userId)
action = action == 'add' ? { $addToSet: { productFavorites: product_key }} : { $pull: { productFavorites: product_key }}
return Meteor.users.update({_id: this.userId}, action)
},
'Products.getByProductKey': function(productFavorites) {
allowOrDeny(this.userId)
if (productFavorites == undefined)
productFavorites = Meteor.users.findOne({_id: this.userId}, {fields: {productFavorites: 1}}).productFavorites
if (productFavorites.length > 0) {
return Products.find(
{ product_key: {$in: productFavorites }, price_100_g_ml: {$ne: null} },
{ sort: {product_name: 1} }).fetch()
} else
return []
},
})
function allowOrDenySubscription(userId, callback) {
if (!userId) {
callback()
return;
}
}
Meteor.publish(null, function() {
if (!this.userId)
return false
return Meteor.users.find({_id: this.userId}, { fields: {
firstName: 1, lastName: 1,
zip: 1, city: 1, street: 1, houseNumber: 1,
phone: 1, iban: 1, bic: 1,
memberId: 1, membershipFee: 1,
productFavorites: 1
}})
}, { is_auto: true })
Meteor.publish('products.RankingList', function() {
const callback = () => this.stop()
allowOrDenySubscription(this.userId, callback)
return getProductRankingList(this)
})
import { Meteor } from 'meteor/meteor';
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
// ... more imports
// Collections
import { AggregatedProductRanking } from '../../../imports/collections';
class ProductRankingList extends Component {
constructor() {
super()
this.state = {
products: [],
productDetails: true,
singleProductDetails: 0,
}
}
getProductsByKeys = (product_keys) => {
Meteor.call('Products.getByProductKey', product_keys, (err, response) => {
if (err)
makeAlert(err.reason, 'danger', 3000)
else {
this.setState({products: response})
console.log(response)
}
})
}
updateProductFavorites = (product_key, action) => {
const { ranking } = this.props
const { product_keys } = ranking[0]
console.log(product_keys)
Meteor.call('Accounts.updateProductFavorites', product_key, action, (err, response) => {
if (err)
makeAlert(err.reason, 'danger', 3000)
else
this.getProductsByKeys(product_keys)
})
}
toggleProductFavorite = (product_key) => {
const { productFavorites } = this.props.user
if (productFavorites.includes(product_key))
this.updateProductFavorites(product_key, 'remove')
else
this.updateProductFavorites(product_key, 'add')
}
mapProductFavorites = () => {
const { products, productDetails, singleProductDetails } = this.state
const { productFavorites } = this.props.user
const { ranking } = this.props
const { rankingList } = ranking[0]
if (products.length == 0)
return <div className="alert alert-primary col-12">No one has favorited any products at the moment, it seems.</div>
products.map((product, i) => {
const { order_number, supplierId } = product
product["count"] = rankingList.find(product => product._id == `${supplierId}_${order_number}`).count
})
products.sort((a, b) => b.count - a.count)
return (
products.map((product, i) => {
if (product.price_100_g_ml) {
var [euro, cent] = product.price_100_g_ml.toFixed(2).toString().split('.')
}
const { product_name, units, trading_unit, certificate, origin, order_number, supplierId, count } = product
const isFavorite = productFavorites.includes(`${supplierId}_${order_number}`) ? 'is-favorite' : 'no-favorite'
return (
<div className="col-lg-6" key={i}>
<div key={i} className="product-card">
<div className="card-header" onClick={() => this.toggleSingleProductDetails(order_number)}>
{product_name}
{/* <span className="fa-layers fa-fw heart-with-count">
<FontAwesomeIcon icon="heart"/>
<div className="fa-layers-text">{count}</div>
</span> */}
</div>
{productDetails || singleProductDetails == order_number ?
<>
<div className="card-body">
{euro ?
<>
<div className="product-actions">
<button className={`btn btn-light btn-lg product-${isFavorite}`}
onClick={() => this.toggleProductFavorite(`${supplierId}_${order_number}`)}>
<FontAwesomeIcon icon="heart"/>
<span className="ml-2">{count}</span>
</button>
</div>
<div className="price-100-g-ml">
<small>pro 100{units == 'kg' ? 'g' : 'ml'}</small><sup></sup>
<big>{euro}</big>.<sup>{cent.substring(0,2)}</sup>
</div>
</> : null}
</div>
<div className="card-footer">
<div className="row">
<div className="col-4">{trading_unit}</div>
<div className="col-4 text-center">{certificate}</div>
<div className="col-4 text-right">{origin}</div>
</div>
</div>
</> : null }
</div>
</div>)
})
)
}
componentDidUpdate(prevProps, prevState) {
const { isLoading, ranking } = this.props
if (ranking.length < 1)
return null
if (!isLoading && (prevProps.ranking != ranking)) {
this.getProductsByKeys(ranking[0].product_keys)
}
}
render() {
const { isLoading, ranking } = this.props
if (isLoading || ranking.length < 1)
return null
return(
<div className="row mt-3">
{this.mapProductFavorites()}
</div>
)
}
}
export default withTracker(() => {
const handle = Meteor.subscribe("products.RankingList")
return {
ranking: AggregatedProductRanking.find({}).fetch(),
user: Meteor.user(),
isLoading: !handle.ready() || !Meteor.user()
}
})(ProductRankingList)
完整堆栈跟踪
// When loading component through history.push()
Exception in flushing DDP buffered writes: Error: Expected to find a document to change
at Object.update (collection.js:207)
at Object.store.<computed> [as update] (livedata_connection.js:310)
at livedata_connection.js:1192
at Array.forEach (<anonymous>)
at livedata_connection.js:1191
at Array.forEach (<anonymous>)
at Connection._performWrites (livedata_connection.js:1187)
at Connection._flushBufferedWrites (livedata_connection.js:1167)
at meteor.js?hash=857dafb4b9dff17e29ed8498a22ea5b1a3d6b41d:1234
// After second call of Meteor.call('Accounts.updateProductFavorites')
Uncaught Error: Expected to find a document to change
at Object.update (collection.js:207)
at Object.store.<computed> [as update] (livedata_connection.js:310)
at livedata_connection.js:1192
at Array.forEach (<anonymous>)
at livedata_connection.js:1191
at Array.forEach (<anonymous>)
at Connection._performWrites (livedata_connection.js:1187)
at Connection._flushBufferedWrites (livedata_connection.js:1167)
at Connection._livedata_data (livedata_connection.js:1133)
at Connection.onMessage (livedata_connection.js:1663)
答案 0 :(得分:0)
事实证明,解决方案非常简单:发生上述错误的原因是聚合结果中的缺少_id
字段:
...
},{
$project: {
_id: 0, // This will give the error.
rankingList: 1,
product_keys: "$product_keys.product_keys"
}
}
], { clientCollection: "aggregatedProductRanking"})
ReactiveAggregate()
(meteor-reactive-aggregate)的文档指出_id
字段可以省略,因为它将由ReactiveAggregate()
自动创建。但是即使删除了_id: 0
,它也仍然无法正常工作。
这是什么工作:
...
},{
$project: {
_id: "whatTheFuckIsGoingOnHere", // Calm down, bro!
rankingList: 1,
product_keys: "$product_keys.product_keys"
}
}
], { clientCollection: "aggregatedProductRanking"})
出色的反应性聚集,只是给a **带来一点痛苦。 我在github repo
中做了一个错误报告