Meteor/React - Обновление подписки при изменении состояния

У меня проблемы с подпиской и React, возможно, я делаю это неправильно.

Вот проблема: я хотел бы создать страницу со списком фильмов, предоставленных коллекцией Монго, также есть фильтр жанра и кнопка "загрузить еще".

Когда я использую только load more, я обновляю публикацию, чтобы пропустить существующий элемент и вернуть новые, это работает хорошо.

Когда я меняю свой жанровый фильтр, я просто обновляю свою публикацию этим фильтром, тоже работает нормально...

Но если я делаю это вместе двумя действиями, например: загружаю больше, затем фильтрую по жанру, результаты выглядят плохо и, кажется, сохраняют старые результаты, нумерация страниц не сбрасывается до значения по умолчанию.

Вот упрощенная версия моего кода.

Публикация на стороне сервера:

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

  }

}

Вот лучший способ сделать это? Спасибо за вашу помощь!

1 ответ

Решение

Я основал, в чем была проблема. При использовании метода loadMore я не прекращаю старую подписку (я хочу сохранить старые элементы и добавлять новые), проблема заключается в том, что подписка "addMore" была сохранена в моем массиве подписок (см. Meteor.default_connection._subscription), Поэтому, когда я меняю фильтры, я закрываю только последнюю подписку "loadMore"...

2 решения:

  • Прокрутите список Meteor.default_connection._subscription, чтобы закрыть старые подписки "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>
    )

  }

}

Надеюсь, это поможет!

Другие вопросы по тегам