121

In React, state is not be updated instantly, so we can use callback in setState(state, callback). But how to do it in Redux?

After calling the this.props.dispatch(updateState(key, value)), I need to do something with the updated state immediately.

Is there any way I can call a callback with the latest state like what I do in React?

Brick Yang
  • 5,388
  • 8
  • 34
  • 45

7 Answers7

140

component should be updated to receive new props.

there are ways to achieve your goal:

1. componentDidUpdate check if value is changed, then do something..

 componentDidUpdate(prevProps){
     if(prevProps.value !== this.props.value){ alert(prevProps.value) }
  }

2. redux-promise ( middleware will dispatch the resolved value of the promise)

export const updateState = (key, value)=>
Promise.resolve({
  type:'UPDATE_STATE',
  key, value
})

then in component

this.props.dispatch(updateState(key, value)).then(()=>{
   alert(this.props.value)
})

2. redux-thunk

export const updateState = (key, value) => dispatch => {
  dispatch({
    type: 'UPDATE_STATE',
    key,
    value,
  });
  return Promise.resolve();
};

then in component

this.props.dispatch(updateState(key, value)).then(()=>{
   alert(this.props.value)
})
neaumusic
  • 10,027
  • 9
  • 55
  • 83
Kokovin Vladislav
  • 10,241
  • 5
  • 38
  • 36
  • 8
    In your `redux-thunk` example, you use `dispatch` as if it is synchronous. Is that the case? – Danny Harding Aug 08 '17 at 14:53
  • 2
    @DannyHarding `dispatch` isn't synchronous. Without the `Promise.resolve()`, `this.props.value` would still return the old value. Some kind of async deferral is still needed (referencing from within a `setTimeout` would also work for example). – Drazen Bjelovuk Aug 23 '18 at 16:38
  • 4
    @DrazenBjelovuk I'm wondering because the updateState function in the redux-thunk example has dispatch(someAction) immediately followed by return Promise.resolve(). The promise resolves immediately, which I imagine would create a race condition between the redux store update and the .then called in the component. Because dispatch doesn't return a promise or accept a callback, I don't think this is an accurate way to know when the redux store has been updated. I think that the answer by just-boris is correct in this case – Danny Harding Aug 23 '18 at 19:10
  • 2
    @DannyHarding Yeah I'd agree that this method is likely not a sure fire guarantee, just illustrating that the dispatch is not synchronous. – Drazen Bjelovuk Aug 24 '18 at 20:59
  • 1
    I'm trying to use the redux-thunk solution here, but I only get `TypeError: _this.props.dispatch is not a function`? – Krillko Mar 28 '19 at 13:02
  • @Krillko I think you are not using arrow functions. If so this modified code should work `export function updateSomeData(data) { // return { type: types.LOAD_SOME_DATA, data }; return dispatch => { dispatch({ type: types.LOAD_SOME_DATA, data }); return Promise.resolve(); }; }` – LordDraagon Jul 21 '19 at 17:13
  • `dipatch` returns a promise. You can chain off that. – Gilbert Oct 21 '21 at 16:21
39

With Hooks API:

useEffect with the prop as input.

import React, { useEffect} from 'react'
import { useSelector } from 'react-redux'

export default function ValueComponent() {
   const value = useSelector(store => store.pathTo.value)

   useEffect(() => {
     console.log('New value', value) 
     return () => {
        console.log('Prev value', value) 
     }

   }, [value])

   return <div> {value} </div>
}
Steve Horn
  • 8,818
  • 11
  • 45
  • 60
YairTawil
  • 3,961
  • 1
  • 22
  • 20
  • 3
    The accepted answer was written before React Hooks. This should be the accepted answer now after the introduction of hooks. It's a more Reactive way to handle changes. – tif Sep 23 '20 at 18:54
  • 7
    No necessarily. What if You want to trigger a function with updated store data on button click BUT, you dont want to trigger that function everytime that particular data has changed? – mkatanski Mar 04 '21 at 11:31
  • 1
    agreed with @mkatanski, use effect trigger everytime, it's not natural to handle that way – Khanh Pham Jun 06 '21 at 17:10
  • @mkatanski to trigger a function with updated store data on button-click, you would just use the selector value in your click handler. Based on op's question performing some action when state changes falls into the category of a side-effect which `useEffect` is intended for. – DannyMoshe Apr 04 '23 at 23:58
  • THIS IS THE BEST ANSWERS, PLEASE IF YOU SCROW DOWN HERE, KNOW THAT THIS ONE SHOULD BE THE CORRECT ONE SO UPVOTE IT. – guilherme victor ramalho natal Jul 25 '23 at 17:01
17

The most important point about React is one-way data flow. In your example that means, that dispatching an action and state change handling should be decoupled.

You shouldn't think like "I did A, now X becomes Y and I handle it", but "What do I do when X becomes Y", without any relation to A. Store state can be updated from mutiple sources, in addition to your component, Time Travel also can change state and it will not be passed through your A dispatch point.

Basically that means that you should use componentWillReceiveProps as it was proposed by @Utro

just-boris
  • 9,468
  • 5
  • 48
  • 84
  • This is the answer op is really looking for (although it looks like he is not aware of this..) – refaelio Jan 03 '18 at 11:35
  • 1
    I agree but this seems to be considered an antipattern: https://hackernoon.com/evil-things-you-do-with-redux-dispatch-in-updating-lifecycle-methods-ad116de882d4 – Giacomo Cerquone Jun 24 '18 at 12:58
  • 1
    what do we do now that `componentWillReceiveProps` is deprecated? `static getDerivedStateFromProps()` doesn't seem a suitable replacement as it can't call a callback, as far as I can tell – M3RS Nov 04 '18 at 08:24
6

You could use subscribe listener and it will be called when an action is dispatched. Inside the listener you will get the latest store data.

http://redux.js.org/docs/api/Store.html#subscribelistener

Pranesh Ravi
  • 18,642
  • 9
  • 46
  • 70
  • 2
    `subscribe` only fires when an action is dispatched. There's no way `subscribe` would let you know if your API call has returned any data.. i think! :D – Mihir Sep 16 '16 at 07:57
  • You could dispatch another action when your request completes (successfully or unsuccessfully). – Jam Jul 13 '17 at 05:55
  • I am not convinced that any of the other solutions proposed here actually work, because I don't see a way for a promise to resolve or a callback to fire after the state is updated. This method gets called for every store update, not just the one that happens after your event, but that shouldn't be too hard to work around. In particular, the link [write a custom observeStore utility](https://github.com/reduxjs/redux/issues/303#issuecomment-125184409) from the page you linked to is very helpful. – Nat Kuhn Jun 16 '19 at 15:45
  • Link Page Not Found – Bharat Modi Jul 05 '20 at 04:54
  • this is preferred while using function based components.. – Mohamed Iqzas Nov 03 '21 at 06:49
3

You can use a thunk with a callback

myThunk = cb => dispatch =>
  myAsyncOp(...)
    .then(res => dispatch(res))
    .then(() => cb()) // Do whatever you want here.
    .catch(err => handleError(err))
acmoune
  • 2,981
  • 3
  • 24
  • 41
2

As a simple solution you can use: redux-promise

But if you using redux-thunk, you should do sth like this:

function addCost(data) {
  return dispatch => {
    var promise1 = new Promise(function(resolve, reject) {
      dispatch(something);
     });
    return promise1;
  }
}
Mohammad P
  • 79
  • 1
  • 4
0

redux's dispatch returns a promise that you can chain off from like so.

     const submissionPromise: any = dispatch(submitFundRequest())
        submissionPromise
            .then((response: any) => {
                if(response.error) {
                    return console.log('There was an error', response)
                }
                Swal.fire({
                    title: 'Submission',
                    text: 'You have successfully submitted the requisition'
                })
            })
            .catch((err: any) => {
                console.log('Submission failed', err)
            })

response.error is only set if the thunk was rejected. In your submitFundRequest thunk, you can do something like this to reject.

export const submitFundRequest = createAsyncThunk(
    'fundRequest/submitFundRequest',
    async function submit(payload, thunkAPI) {
        try {
            ...
        } catch(e: any) {
            const error = { message: e.response.statusMessage }
            return thunkAPI.rejectWithValue(error)
        }
    }
)

Gilbert
  • 2,699
  • 28
  • 29