2

I am trying to use react hooks to make a Table component that displays rows of data from an API based on a set of filters that the user can choose. I want to make a new call to fetch data whenever the user clicks an 'Apply Filters' button, not when the user makes changes to the filters.

I am using context to manage the 'filters' state and a 'lastFetched' state which tracks when the user last clicked the 'Apply Filters' button (as well as other states on the page). Updates to the context are made via the useReducer hook and its dispatch method (see here).

The data fetching occurs in a useEffect hook that reruns whenever the 'lastFetched' state changes. This appears to be working correctly; however, the effect references other values from the context (i.e. the filters) that are not included in the dependencies. I am aware of the exhaustive-deps eslint rule, and I am concerned that I am not handling the hook's dependencies correctly.

const Table = () => {
    const [context, dispatch] = useTableContext(); // implemented with createContext and useReducer
    const { filters, lastFetched } = context;

    useEffect(() => {
        if (!filters.run) {
            return;
        }

        dispatch({ type: 'FETCH_DATA_BEGIN' });
        const params = convertContextToParams(context); // this is lazy, but essentially just uses the the filters and some other state from the context 

        API.fetchData(params)
            .then((data) => {
                dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data.results });
            })
            .catch((e) => {
                dispatch({ type: 'FETCH_DATA_FAILURE', payload: e.response.data.message });
            });

        return () => { ... some cleanup... }; 
    }, [lastFetched]); // <== This is the part in question

 return <...some jsx.../>
};

Again, this appears to be working, but according to the react docs, it seems I should be including all the values from the context used in the hook in the hook's dependencies in order to prevent stale references. This would cause the logic to break, since I don't want to fetch data whenever the filters change.

My question is: when the user clicks 'Apply Filters', updates context.lastFetched, and triggers the useEffect hook, will the hook be referencing stale filter state from the context? If so, why? Since the effect is rerun whenever the button is clicked, and all the state updates are done via a reducer, does the usual danger of referencing stale variables in a closure still apply?

Any guidance appreciated!

Note: I have thought about using useRef to prevent this issue, or perhaps devising some custom async middleware to fetch data on certain dispatches, but this is the solution I currently have.

Ethan Kelley
  • 113
  • 1
  • 8

1 Answers1

0

I am not an expert but I would like to provide my takes. According to my understanding of how Context works, you will not get stale filter data with the current implementation. useReducer updates the state with a new object which will trigger Table to be re-render.

Also, Table component doesn't really care about filter data unless lastFetched is changed by a click event. If lastFetched is changed, all the Consumer of TableContext will be re-render again. You should not get stale filter data either.

Andrew Zheng
  • 2,612
  • 1
  • 20
  • 25
  • Thanks, this makes sense to me. If that’s the case, I wonder if this is a reasonable use case for disabling the exhaustive-does eslint rule. – Ethan Kelley Sep 16 '19 at 02:37
  • I think it makes sense to make `exhaustive-does` a warning with the way you implemented. If you change your logic to filter when filters data is updated, you might run into stale data issue depends on how you handle filters data update. The useEffect optimization does shallow comparisons. This links explain how shallow comparison works in more details. https://stackoverflow.com/questions/36084515/how-does-shallow-compare-work-in-react i hope it helps – Andrew Zheng Sep 16 '19 at 13:49
  • Right, I certainly would have to re-enable the rule for this line if I change the logic. But if the rule stays I will have trouble getting around my git prepush linting hooks lol. Anyways, thanks for the input. Still seems to be some uncertainty around best practices regarding data fetching with hooks. – Ethan Kelley Sep 16 '19 at 15:47
  • @EthanKelley in my project. I have tried to move the fetching logic to the Context. I wrote an article related to my approach. You can have a read if you are interested. I wish I can help more. https://medium.com/@xiangzheng/use-usereducer-as-usestate-replacement-to-handle-complicate-local-state-b107d7c3807e – Andrew Zheng Sep 17 '19 at 14:09