React + Redux: proper way to handle after-dispatch logic

Viewed 1098

I have a component with some internal state (e.g. isLoading) which has access to redux data. In this component I'd like to dispatch a thunk action (api request) resulting in redux data change. After the thunk is completed I need to change the state of my component. As I see it, there are two ways to do so:

  1. Use the promise return by the thunk and do all I need there, e.g.
handleSaveClick = (id) => {
    const { onSave } = this.props;
    this.setState({ isLoading: true });
    onSave(id).then(() => this.setState({ isLoading: false }));
};
  1. Pass a callback to the thunk and fire it from the thunk itself, e.g.
  handleSaveClick = (id) => {
    const { onSave } = this.props;
    this.setState({ isLoading: true });
    onSave(id, this.onSaveSuccess);
  };

Which one is the correct way to do so?

2 Answers

The safer way is to use the promise implementation, as you'll be sure that function will only run after the promise has been resolved. The second implementation has no inherent flaws, but if anything in your thunk is async, then it will not work correctly since it'll run once the code is reached, not when the code above it finishes executing. When handling anything that can be async (server requests/loading data/submitting data), it's always safer to use Promise implementations.

Probably the best practice for updating component level state or running a callback function based on changing redux state (or any props/state changes for that matter) is to use componentDidUpdate or useEffect:

componentDidUpdate(prevProps, prevState){
     if(prevProps.someReduxState !== this.props.someReduxState && this.state.isLoading){
        setState({isLoading:false})
     }
}

With useEffect:

useEffect(()=>{
    if(!props.someReduxState){
         setLoading(true)
    } else {
         setLoading(false)
    }
},[props.someReduxState])

However, I might recommend a different approach (depending on the goal especially on initial data fetching) that manages the loading of state within redux:

Initialize your redux state with a loading value instead:

export default someReduxState = (state = {notLoaded:true}, action) => {
    switch (action.type) {
        case actions.FETCH_SOME_REDUX_STATE:
            return action.payload;
        default:
            return state;
    }
}

then in your component you can check:

if (this.props.someReduxState.notLoaded ){
    // do something or return loading components
} else {
    // do something else or return loaded components
}
Related