Meteor/React - 状态改变时更新订阅
Meteor/React - Update subscription on state change
我在订阅和 React 方面遇到了麻烦,也许我没有以正确的方式进行。
这是问题所在:
我想创建一个页面,其中包含 mongo 集合提供的电影列表,还有一个流派过滤器和一个 "load more" 按钮。
当我仅使用加载更多时,我更新发布以跳过现有项目和 return 新项目,效果很好。
当我更改流派过滤器时,我只需使用该过滤器更新我的出版物,也可以正常工作...
但是如果我一起执行这两个操作,例如:加载更多,然后按类型过滤,结果看起来很糟糕并且似乎保留旧结果,分页不会重置为默认值。
这是我的代码的简化版本。
服务器端发布:
Meteor.publish('movies.published', function ( sort = null, skip = 0, limit = 20, filters = {} ) {
if ( ! sort || typeof sort !== 'object' ) {
sort = {releaseDate: -1};
}
let params = Object.assign({
webTorrents: {
$exists: true,
$ne: []
},
status: 1,
}, filters);
console.log( '----------------Publishing--------------------');
let cursor = Movies.find(params, {
sort: sort,
skip: skip,
limit: limit
});
// Test publication
// Count is always good
console.log(cursor.fetch().length);
return cursor;
});
客户端组件:
import React from "react";
import TrackerReact from "meteor/ultimatejs:tracker-react";
import Movies from "/imports/api/movies/movies.js";
import Genres from "/imports/api/genres/genres.js";
const itemPerPage = 1; // let's say 1 item to simplify our test
const defaultOrder = 'releaseDate';
export default class Home extends TrackerReact(React.Component) {
constructor(props) {
super(props);
let options = Object.assign(Meteor.settings.public.list.options, {genres: Genres.find()});
this.state = {
subscription: {
movies: Meteor.subscribe('movies.published', {[defaultOrder]: -1}, 0, itemPerPage),
},
filters: {},
sort: defaultOrder,
options: options,
};
}
componentWillUnmount() {
this.state.subscription.movies.stop();
}
filterByGenre(event) {
let filterGenre = {
['genres.slug']: event.target.value !== '' ? event.target.value : {$ne: null}
};
this.updateFilters(filterGenre);
}
updateFilters( values ) {
// Merge filters
let filters = Object.assign(this.state.filters, values);
console.log('updating filters', filters);
// Update subscription with filters values
// here i must reset the pagination
this.state.subscription.movies.stop();
let newSubscription = Object.assign({}, this.state.subscription, {
movies: Meteor.subscribe('movies.published', {[this.state.sort]: -1}, 0, itemPerPage, filters),
});
this.setState({
subscription: newSubscription,
filters: filters,
})
}
loadMore( skip ) {
// Add an item
let newSubscription = Object.assign({}, this.state.subscription, {
movies: Meteor.subscribe('movies.published', {[this.state.sort]: -1}, skip, itemPerPage, this.state.filters),
});
this.setState({
subscription: newSubscription,
});
}
render() {
if ( ! this.state.subscription.movies.ready() ) {
return ( <div>loading...</div> );
}
// Get our items
let movies = Movies.find().fetch();
return (
<div>
<div className="container-fluid">
{/* Filters */}
<form className="form form-inline">
<div className="form-group m-r m-2x">
<select className="form-control" value={this.state.filters['genres.slug'] && this.state.filters['genres.slug']} onChange={this.filterByGenre.bind(this)}>
<option value="">Genres</option>
{this.state.options.genres.map((genre, key) => {
return <option key={key} value={genre.slug}>{genre.name}</option>
})}
</select>
</div>
</form>
<hr />
{/* list */}
<div className="row list">
{movies.map((movie) => {
return (
<div key={movie._id} className="col-xs-2">{movie.title}</div>
)
})}
</div>
{/* Load more */}
<button id="load-more" type="button" className="btn btn-sm btn-info" onClick={this.loadMore.bind(this, movies.length)}>
Load more
</button>
</div>
<div className="col-xs-3 pull-right text-right">{movies.length} movies</div>
<hr />
</div>
)
}
}
这里有更好的方法吗?
谢谢你的帮助 !
我发现是什么问题。
使用 loadMore 方法时,我没有停止旧订阅(我想保留旧项目并添加新项目),问题是 "addMore" 订阅保留在我的订阅数组中(参见 Meteor.default_connection._subscriptions).因此,当我更改过滤器时,我只关闭最后一个 "loadMore" 订阅...
2个解决方案:
- 循环 Meteor.default_connection._subscriptions,关闭旧的 "LoadMore" 订阅。
- 在数组中保留旧项目的副本,并在更新订阅后将它们与新项目合并,这就是我所做的。
更新客户端代码:
import React from "react";
import TrackerReact from "meteor/ultimatejs:tracker-react";
import Movies from "/imports/api/movies/movies.js";
import Genres from "/imports/api/genres/genres.js";
const itemPerPage = 1;
const defaultOrder = 'releaseDate';
export default class Home extends TrackerReact(React.Component) {
constructor(props) {
super(props);
let options = Object.assign(Meteor.settings.public.list.options, {genres: Genres.find()});
this.state = {
subscription: {
movies: Meteor.subscribe('movies.published', {[defaultOrder]: -1}, 0, itemPerPage),
moviesCount: Meteor.subscribe('movies.count'),
},
skip: 0,
filters: {},
sort: defaultOrder,
options: options,
};
// our local data
this.data = [];
this.previous = [];
}
componentWillUnmount() {
this.state.subscription.movies.stop();
}
filterByGenre(event) {
let filterGenre = {
['genres.slug']: event.target.value !== '' ? event.target.value : {$ne: null}
};
this.updateFilters(filterGenre);
}
updateFilters( values ) {
// Merge filters
let filters = Object.assign(this.state.filters, values);
// Update subscription ( reset pagination )
this.state.subscription.movies.stop();
this.state.subscription.moviesCount.stop();
let newSubscription = Object.assign({}, this.state.subscription, {
movies: Meteor.subscribe('movies.published', {[this.state.sort]: -1}, 0, itemPerPage, filters),
moviesCount: Meteor.subscribe('movies.count', filters),
});
this.setState({
subscription: newSubscription,
filters: filters,
skip: 0,
});
}
loadMore() {
// Keep a copy of previous page items
this.previous = this.data;
// Update subscription
this.state.subscription.movies.stop();
let newSubscription = Object.assign({}, this.state.subscription, {
movies: Meteor.subscribe('movies.published', {[this.state.sort]: -1}, this.previous.length, itemPerPage, this.state.filters)
});
this.setState({
subscription: newSubscription,
skip: this.previous.length,
});
}
getMovies() {
// Wait subscription ready to avoid replacing items
if ( ! this.state.subscription.movies.ready() ) {
return this.previous;
}
// Get new data and merge with old ones
let newData = Movies.find().fetch();
this.data = this.previous.concat(newData);
// Reset previous array
this.previous = [];
return this.data;
}
render() {
if ( ! this.state.subscription.movies.ready() && ! this.previous.length ) {
return ( <div>loading...</div> );
}
// Get our items
let movies = this.getMovies();
return (
<div>
<div className="container-fluid">
{/* Filters */}
<form className="form form-inline">
<div className="form-group m-r m-2x">
<select className="form-control" value={this.state.filters['genres.slug'] && this.state.filters['genres.slug']} onChange={this.filterByGenre.bind(this)}>
<option value="">Genres</option>
{this.state.options.genres.map((genre, key) => {
return <option key={key} value={genre.slug}>{genre.name}</option>
})}
</select>
</div>
</form>
<hr />
{/* list */}
<div className="row list">
{movies.map((movie) => {
return (
<div key={movie._id} className="col-xs-2">{movie.title}</div>
)
})}
</div>
{/* Load more */}
<div className="row">
<div className="col-xs-12 text-center">
{Counts.get('movies.count') > movies.length &&
<button id="load-more" type="button" className="btn btn-sm btn-info" onClick={this.loadMore.bind(this)}>
Load more
</button>
}
</div>
</div>
</div>
<div className="col-xs-3 pull-right text-right">{Counts.get('movies.count')} movies</div>
<hr />
</div>
)
}
}
希望对您有所帮助!
我在订阅和 React 方面遇到了麻烦,也许我没有以正确的方式进行。
这是问题所在: 我想创建一个页面,其中包含 mongo 集合提供的电影列表,还有一个流派过滤器和一个 "load more" 按钮。
当我仅使用加载更多时,我更新发布以跳过现有项目和 return 新项目,效果很好。
当我更改流派过滤器时,我只需使用该过滤器更新我的出版物,也可以正常工作...
但是如果我一起执行这两个操作,例如:加载更多,然后按类型过滤,结果看起来很糟糕并且似乎保留旧结果,分页不会重置为默认值。
这是我的代码的简化版本。
服务器端发布:
Meteor.publish('movies.published', function ( sort = null, skip = 0, limit = 20, filters = {} ) {
if ( ! sort || typeof sort !== 'object' ) {
sort = {releaseDate: -1};
}
let params = Object.assign({
webTorrents: {
$exists: true,
$ne: []
},
status: 1,
}, filters);
console.log( '----------------Publishing--------------------');
let cursor = Movies.find(params, {
sort: sort,
skip: skip,
limit: limit
});
// Test publication
// Count is always good
console.log(cursor.fetch().length);
return cursor;
});
客户端组件:
import React from "react";
import TrackerReact from "meteor/ultimatejs:tracker-react";
import Movies from "/imports/api/movies/movies.js";
import Genres from "/imports/api/genres/genres.js";
const itemPerPage = 1; // let's say 1 item to simplify our test
const defaultOrder = 'releaseDate';
export default class Home extends TrackerReact(React.Component) {
constructor(props) {
super(props);
let options = Object.assign(Meteor.settings.public.list.options, {genres: Genres.find()});
this.state = {
subscription: {
movies: Meteor.subscribe('movies.published', {[defaultOrder]: -1}, 0, itemPerPage),
},
filters: {},
sort: defaultOrder,
options: options,
};
}
componentWillUnmount() {
this.state.subscription.movies.stop();
}
filterByGenre(event) {
let filterGenre = {
['genres.slug']: event.target.value !== '' ? event.target.value : {$ne: null}
};
this.updateFilters(filterGenre);
}
updateFilters( values ) {
// Merge filters
let filters = Object.assign(this.state.filters, values);
console.log('updating filters', filters);
// Update subscription with filters values
// here i must reset the pagination
this.state.subscription.movies.stop();
let newSubscription = Object.assign({}, this.state.subscription, {
movies: Meteor.subscribe('movies.published', {[this.state.sort]: -1}, 0, itemPerPage, filters),
});
this.setState({
subscription: newSubscription,
filters: filters,
})
}
loadMore( skip ) {
// Add an item
let newSubscription = Object.assign({}, this.state.subscription, {
movies: Meteor.subscribe('movies.published', {[this.state.sort]: -1}, skip, itemPerPage, this.state.filters),
});
this.setState({
subscription: newSubscription,
});
}
render() {
if ( ! this.state.subscription.movies.ready() ) {
return ( <div>loading...</div> );
}
// Get our items
let movies = Movies.find().fetch();
return (
<div>
<div className="container-fluid">
{/* Filters */}
<form className="form form-inline">
<div className="form-group m-r m-2x">
<select className="form-control" value={this.state.filters['genres.slug'] && this.state.filters['genres.slug']} onChange={this.filterByGenre.bind(this)}>
<option value="">Genres</option>
{this.state.options.genres.map((genre, key) => {
return <option key={key} value={genre.slug}>{genre.name}</option>
})}
</select>
</div>
</form>
<hr />
{/* list */}
<div className="row list">
{movies.map((movie) => {
return (
<div key={movie._id} className="col-xs-2">{movie.title}</div>
)
})}
</div>
{/* Load more */}
<button id="load-more" type="button" className="btn btn-sm btn-info" onClick={this.loadMore.bind(this, movies.length)}>
Load more
</button>
</div>
<div className="col-xs-3 pull-right text-right">{movies.length} movies</div>
<hr />
</div>
)
}
}
这里有更好的方法吗? 谢谢你的帮助 !
我发现是什么问题。 使用 loadMore 方法时,我没有停止旧订阅(我想保留旧项目并添加新项目),问题是 "addMore" 订阅保留在我的订阅数组中(参见 Meteor.default_connection._subscriptions).因此,当我更改过滤器时,我只关闭最后一个 "loadMore" 订阅...
2个解决方案:
- 循环 Meteor.default_connection._subscriptions,关闭旧的 "LoadMore" 订阅。
- 在数组中保留旧项目的副本,并在更新订阅后将它们与新项目合并,这就是我所做的。
更新客户端代码:
import React from "react";
import TrackerReact from "meteor/ultimatejs:tracker-react";
import Movies from "/imports/api/movies/movies.js";
import Genres from "/imports/api/genres/genres.js";
const itemPerPage = 1;
const defaultOrder = 'releaseDate';
export default class Home extends TrackerReact(React.Component) {
constructor(props) {
super(props);
let options = Object.assign(Meteor.settings.public.list.options, {genres: Genres.find()});
this.state = {
subscription: {
movies: Meteor.subscribe('movies.published', {[defaultOrder]: -1}, 0, itemPerPage),
moviesCount: Meteor.subscribe('movies.count'),
},
skip: 0,
filters: {},
sort: defaultOrder,
options: options,
};
// our local data
this.data = [];
this.previous = [];
}
componentWillUnmount() {
this.state.subscription.movies.stop();
}
filterByGenre(event) {
let filterGenre = {
['genres.slug']: event.target.value !== '' ? event.target.value : {$ne: null}
};
this.updateFilters(filterGenre);
}
updateFilters( values ) {
// Merge filters
let filters = Object.assign(this.state.filters, values);
// Update subscription ( reset pagination )
this.state.subscription.movies.stop();
this.state.subscription.moviesCount.stop();
let newSubscription = Object.assign({}, this.state.subscription, {
movies: Meteor.subscribe('movies.published', {[this.state.sort]: -1}, 0, itemPerPage, filters),
moviesCount: Meteor.subscribe('movies.count', filters),
});
this.setState({
subscription: newSubscription,
filters: filters,
skip: 0,
});
}
loadMore() {
// Keep a copy of previous page items
this.previous = this.data;
// Update subscription
this.state.subscription.movies.stop();
let newSubscription = Object.assign({}, this.state.subscription, {
movies: Meteor.subscribe('movies.published', {[this.state.sort]: -1}, this.previous.length, itemPerPage, this.state.filters)
});
this.setState({
subscription: newSubscription,
skip: this.previous.length,
});
}
getMovies() {
// Wait subscription ready to avoid replacing items
if ( ! this.state.subscription.movies.ready() ) {
return this.previous;
}
// Get new data and merge with old ones
let newData = Movies.find().fetch();
this.data = this.previous.concat(newData);
// Reset previous array
this.previous = [];
return this.data;
}
render() {
if ( ! this.state.subscription.movies.ready() && ! this.previous.length ) {
return ( <div>loading...</div> );
}
// Get our items
let movies = this.getMovies();
return (
<div>
<div className="container-fluid">
{/* Filters */}
<form className="form form-inline">
<div className="form-group m-r m-2x">
<select className="form-control" value={this.state.filters['genres.slug'] && this.state.filters['genres.slug']} onChange={this.filterByGenre.bind(this)}>
<option value="">Genres</option>
{this.state.options.genres.map((genre, key) => {
return <option key={key} value={genre.slug}>{genre.name}</option>
})}
</select>
</div>
</form>
<hr />
{/* list */}
<div className="row list">
{movies.map((movie) => {
return (
<div key={movie._id} className="col-xs-2">{movie.title}</div>
)
})}
</div>
{/* Load more */}
<div className="row">
<div className="col-xs-12 text-center">
{Counts.get('movies.count') > movies.length &&
<button id="load-more" type="button" className="btn btn-sm btn-info" onClick={this.loadMore.bind(this)}>
Load more
</button>
}
</div>
</div>
</div>
<div className="col-xs-3 pull-right text-right">{Counts.get('movies.count')} movies</div>
<hr />
</div>
)
}
}
希望对您有所帮助!