Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Paginate date-specific results from an API with React and Redux

I want to show some news in my React app using Redux.

The problem is that I want to show the news for individual dates and I want to paginate the news.

In my API I print

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

I know how to make a simple pagination, but I don't know how the API should work if I want to be able to show news for different dates in my app.

Until now (without dates), I have just kept a state news and pagination in my Redux reducer, and then checked if the page number equals the total number of pages to determine whether it should try loading more news.

But now that I potentially have many different dates and I want to keep all the news in the Redux store, I don't know how to structure it.

I can keep my API as it is, since filtering with GET parameter ?date=15-09-2017 will just decrease the number of news in the API result.

But would it still be possible to just keep all the news in an array in a news variable in my reducer or do I have to structure it to be something like

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

in order to keep track of the pagination for every single date?

like image 593
Jamgreen Avatar asked Sep 15 '17 09:09

Jamgreen


2 Answers

A think a structure where you store your your news by id and ids per date would be good and flexible structure:

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

You can implement simple selectors for this structure:

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);
    });
}
like image 79
floriangosse Avatar answered Sep 20 '22 17:09

floriangosse


I would recommend to keep storing all news in a single place (news in your reducer), and use selectors to compute derived data.

To achieve this, you will need to store the current date (I suppose it's a some kind of filter in your app) in Redux store, so it can be used in your selector.

So your reducer will look like:

{
  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's not needed in case of infinite scroll
  }
}

And selectors:

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

Then you can use newsByDatePagedSelector selector to get the needed news in your container:

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

When user reach the last news by some date, you can request next 10 news from your API as usual with filter ?date=15-09-2017 and put them to your store. Redux and reselect will bring them to NewsContainer as newsByDate prop.

like image 26
quotesBro Avatar answered Sep 23 '22 17:09

quotesBro