2

In an app with react, redux, and redux saga, where is the best place to put code which initiates a fetch of data, especially in a component which includes paging (and where that data for the current page is cached in the store and should only be fetched if it's not already cached)?

I can see from this question that one suggestion is to do this in the component itself (e.g. in componentDidMount), which is what I've done (except I've used a functional component using the useEffect hook instead), but the logic is now spread over a couple of places and doesn't seem clean.

Say a user clicks some link which takes them somewhere (e.g. using react-router) which loads a component showing a list of users. When that happens we want to initiate a fetch of users, and show some progress (e.g. a spinner) till the data is available. We also want the same thing to happen when a user clicks the paging controls to fetch the next page of users, etc.

Here's what I currently have, which I don't think is quite right:

  • Initial population...
    • The Users (functional) component loads.
    • This receives props for page (the index of the current page) and users (an array of users to show: initially null).
    • It uses the useEffect hook to execute the side-effect of initiating a fetch. i.e. it dispatches a USERS_FETCH_REQUESTED action.
    • This is handled by a saga, which will eventually respond with USERS_FETCH_SUCCESS or USERS_FETCH_ERROR.
    • This will lead to the component being re-rendered, this time with the users array populated.
  • Paging...
    • The Users component renders paging controls (buttons to go to the next page, last page, etc).
    • Those buttons call a setPage function passed into props, to handle the page change.
    • This dispatches a SET_PAGE action to the store.
    • The reducer then changes the page value in the application state.
    • This causes a re-render of the Users component.
    • When re-rendered, because this uses the useEffect hook, and this has page as a dependency, this will kick off the fetch request as before.

The above all works, but maybe seems a bit messy because a UI component is responsible for causing a data fetch to occur. Also the fact that calling the setPage action has nothing in it to initiate a user fetch, when logically maybe it should. But if we do put that logic in that action, how do we then get the initial load to occur, when this isn't caused by a page change.

An important point to note is that we don't always want to fetch the data when the component is loaded: we only want to do that if the application's state in the store doesn't already contain the users for the current page. e.g. if page 1 of users is loaded, then the user navigates off to elsewhere in the app, then comes back to this component: the component should then just show the users already cached in the store.

Below is roughly what the code might look like with the current approach:

const Users: React.FC<{
    page: number,
    users: User[] | null,
    isFetching: boolean,
    fetchError?: Error,
    setPage: (page: number) => void,
    fetchUsers: (page: number) => void
}> = ({page, users, isFetching, fetchError, setPage, fetchUsers}): JSX.Element => {

    useEffect(() => {
        // Fetch users if we don't already have them in the props.
        !users && fetchUsers(page)
    }, [users, page, fetchUsers])

    return (
        <div>
            <button id="previousPageButton" onClick={() => setPage(page - 1)}>&lt;</button>
            <button id="nextPageButton" onClick={() => setPage(page + 1)}>&gt;</button>
            {fetchError
                ? <div>Error fetching users: {fetchError.message}</div>
                : (isFetching || !users
                    ? <div>Fetching...</div>
                    : users.map(user => <UserRow user={user} key={user.id}/>))
Yoni Gibbs
  • 4,441
  • 1
  • 13
  • 31

1 Answers1

0

The best way to clean up your approach is to connect lower level components to the redux store. I would suggest having two components here:

  1. User Component - this displays the list you want to show
  2. Page navigation component - this displays the buttons for page navigation

If you have the page number stored in redux state, as well as the list of User's info that needs to be displayed, then you can connect both the User component and the page number component separately to state.

This way the mapStateToProps of the Users Component, will only look for the list to display, and the mapStateToProps of the Page component will only look for the page number. When you change a page number, the Page component will fire two events - one that changes the page number in state, and one that calls the hook to refetch the new list of Users to display. This way, when the components will re-render independently as they are connected to separate parts of the redux store.

S. Taylor
  • 41
  • 2
  • So where do we put the logic for the initial population? In the User component using the `useEffect` hook as in the code snippet above? And how do we avoid duplication of any logic around fetching the users being required on initial population, and on page change? – Yoni Gibbs May 24 '19 at 16:06