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>
    )

  }

}

希望对您有所帮助!