Paginate date-specific результаты API с React и Redux

Я хочу показать некоторые новости в своем приложении React с помощью Redux.

Проблема в том, что я хочу показывать новости для отдельных дат, и я хочу разбивать новости на новости.

В моем API я печатаю

{
  pagination: {
    count: 1000,
    size: 10,
    page: 1,
    pages: 100
  },
  news: [
    ..
  ]
}

Я знаю, как сделать простую разбивку на страницы, но я не знаю, как должен работать API, если я хочу иметь возможность показывать новости для разных дат в своем приложении.

До сих пор (без дат) я только что сохранил состояние news и pagination в моем редукторе Redux, а затем проверял, равен ли номер страницы общему количеству страниц, чтобы определить, следует ли попробовать загружать больше новостей.

Но теперь, когда у меня потенциально много разных дат, и я хочу сохранить все новости в магазине Redux, я не знаю, как его структурировать.

Я могу сохранить API как есть, так как фильтрация с параметром GET ?date=15-09-2017 просто уменьшит количество новостей в результате API.

Но будет ли возможно сохранять все новости в массиве в переменной news в моем редукторе или мне нужно создать его как нечто вроде

news: {
  '15-09-2017': {
    news: [...],
    pagination: {}
  },
  ...
}

чтобы отслеживать разбивку на страницы для каждой отдельной даты?

Ответ 1

Думайте, что структура, в которой вы храните свои новости по id и идентификаторы в день, будет хорошей и гибкой структурой:

{
    "byId": {
        "10": { /* News */ },
        "14": { /* News */ },
        /* ... */
    },
    "listsByDate": {
        "2017-08-24": {
            "total": 100,
            "pageSize": 10,
            "page": 2,
            "items": [
                10,
                14,
                /* ... */
            ]
        }
    }
}

Вы можете реализовать простые селектора для этой структуры:

const getNewsById = (state, id) => state.byId[id];

const getListByDate = (state, date) => state.listByDate[date];

const getPageOfList = (state, data) => getListByDate(state, date).page

/* ... */

const getNewsByDate = (state, date) => {
    return getListByDate(state, data).items.map((id) => {
        return getNewsById(state, id);
    });
}

Ответ 2

Я бы рекомендовал хранить все новости в одном месте (news в вашем редукторе) и использовать селекторы для вычисления полученных данных.

Для этого вам нужно будет сохранить текущую дату (я полагаю, это какой-то фильтр в вашем приложении) в хранилище Redux, поэтому ее можно использовать в вашем селекторе.

Итак, ваш редуктор будет выглядеть так:

{
  news: [], // you can keep all the news here
  date: '15-09-2017', // selected date
  page: 1, // current page
  pagination: {
    '15-09-2017': { ... }, // pagination info by date.
    // You will need this only if you use the classic pager (with page numbers in UI)
    // it not needed in case of infinite scroll
  }
}

И селекторов:

import { createSelector } from 'reselect';

// select all news
const newsSelector = () => (state) => state.get('news');

// select date
const dateSelector = () => (state) => state.get('date');

// select page
const pageSelector = () => (state) => state.get('page');

// select news by date
const newsByDateSelector = () => createSelector(
  newsSelector(),
  dateSelector(),
  (news, date, page) => news.filter((newsItem) => newsItem.get('date') === date)
);

const pageSize = 10;
const newsByDatePagedSelector = () => createSelector(
  newsByDateSelector(),
  pageSelector(),
  (news, page) => news.slice(pageSize  * (page - 1), pageSize * page)
);

Затем вы можете использовать селектор newsByDatePagedSelector для получения необходимых новостей в вашем контейнере:

import React from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';

export class NewsContainer extends React.PureComponent {
  componentDidMount() {
    // news by selected date are not loaded yet
    if (!this.props.news) {
      this.loadNews();
    }
  }

  componentWillReceiveProps(nextProps) {
    // user navigated to the next page
    if (this.props.news && !nextProps.news) {
      this.loadNews();
    }
  }

  loadNews() {
    // fetch next 10 news from server
  }

  render() {
    return (
      <div>
        this.props.news.map((newsItem) => ...)
      </div>
    );
  }
}

const mapStateToProps = createStructuredSelector({
  news: newsByDatePagedSelector(),
});

export default connect(mapStateToProps)(NewsContainer);

Когда пользователь достигает последних новостей к какой-то дате, вы можете запросить следующие 10 новостей из вашего API, как обычно, с помощью фильтра ?date=15-09-2017 и поместить их в свой магазин. Redux и reselect приведут их к NewsContainer как newsByDate prop.