What is the best way to use redux action in useEffect?

Viewed 23936

I have a React Component like shown bellow (some parts are ommited) and I'm using redux for state management. The getRecentSheets action contains an AJAX request and dispatches the response to redux which updates state.sheets.recentSheets with the response's data.

All this works as expected, but on building it throws warning about useEffect has a missing dependency: 'getRecentSheets'. But if I add getRecentSheets to useEffect's dependency array it starts to rerun indefinitely and thus freezes the app.

I've read React documentation about the useEffect hook https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies but it doesn't provide a good example for such usecase. I suppose it is something with useCallback or react-redux useDispatch, but without examples I'm not sure how to implement it.

Can someone please tell me what the most concise and idiomatic way to use redux action in useEffect would be and how to avoid warnings about missing dependencies?

import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import SheetList from '../components/sheets/SheetList';
import { getRecentSheets } from '../store/actions/sheetsActions';

const mapStateToProps = (state) => {
    return {
        recentSheets: state.sheets.recentSheets,
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        getRecentSheets: () => dispatch(getRecentSheets()),
    }
}

const Home = (props) => {
    const {recentSheets, getRecentSheets} = props;

    useEffect(() => {
        getRecentSheets();
    }, [])

    return <SheetList sheets={ recentSheets } />
};

export default connect(mapStateToProps, mapDispatchToProps) (Home);
3 Answers

After all, it seems that correct way will be as follows:

// ...
import { useDispatch } from 'react-redux';
import { getRecentSheets } from '../store/actions/sheetsActions';

const Home = props => {
    const dispatch = useDispatch();

    useEffect(() => {
        dispatch(getRecentSheets());
    }, [dispatch])

    // ...
};

This way it doesn't complain about getRecentSheets missing in dependencies array. As I understood from reading React doc on hooks that's because it's not defined inside the component. Though I'm new to frontend and I hope I didn't mess something up here.

Passing an empty array in your hook tells React your hook function will not have any dependent values from either props or state.

useEffect(() => {
   getRecentSheets();
}, [])

The infinite loop arises when you declare the dispatcher as a dependency on the hook. When the component is initialized, props.recentSheets hasn't been set, and will rerender once you make your AJAX call.

useEffect(() => {
   getRecentSheets();
}, [getRecentSheets])

You could try something like this:

const Home = ({recentSheets}) => {
    const getRecentSheetsCallback = useCallback(() => {
       getRecentSheets();
    })

    useEffect(() => {
        getRecentSheetsCallback();
    }, [recentSheets]) // We only run this effect again if recentSheets changes


    return <SheetList sheets={ recentSheets } />
};

No matter how many times Homes re-renders, you retain the memoized function to your dispatch call.

Alternatively, you may have encountered find similar patterns utilizing local state and then make your effect "depend" on sheets.

const [sheets, setSheets] = useState(recentSheets)

Hope this helps

I would add a check to see if recentSheets exists or not, using that as my dependency.

useEffect(() => {
    if (!recentSheets) getRecentSheets();
}, [recentSheets])
Related