Redux Thunk: Asynchronous Actions - Step-by-step explanation needed

Tell us what’s happening:
I did what the challenge asked me to do. The test passed. I still don’t understand this concept. Trying to look this up YouTube or reading about it only makes things more confusing.

Can somebody tell me if I’m understanding this wrong:

If I know I need to request data from, say, a server, and I know this is going to take time, then I don’t want to immediately update the Redux store because this will cause an error (updating with no data).

So I set up a thunk, get the middleware set up etc.

Now, instead of directly dispatching the store to update data like before, I break it up into 2 separate dispatches:

  1. dispatch telling store I got data incoming, so wait for it
  2. dispatch telling store I got the data, so pass the data to the reducer so it could do its thing

Therefore, I wouldn’t call requestingData() anywhere in the code except inside the thunk? Instead, I would call the thunk like so:

handleAsync();

And this thunk will automatically send out the first dispatch requestingData().

The thunk will then wait until it gets a response in the form of a dispatch, which is entered as an argument in the function returned by the thunk? As in:

handleAsynch()(incoming dispatch); --> this looks like a curried function, so it takes in a dispatch as an argument, right?

Then the thunk will send out the final dispatch receivedData()?

Your code so far


const REQUESTING_DATA = 'REQUESTING_DATA'
const RECEIVED_DATA = 'RECEIVED_DATA'

const requestingData = () => { return {type: REQUESTING_DATA} }
const receivedData = (data) => { return {type: RECEIVED_DATA, users: data.users} }

const handleAsync = () => {
  return function(dispatch) {
    // dispatch request action here
    store.dispatch(requestingData());
    setTimeout(function() {
      let data = {
        users: ['Jeff', 'William', 'Alice']
      }
      // dispatch received data action here
    store.dispatch(receivedData(data));
    }, 2500);
  }
};

const defaultState = {
  fetching: false,
  users: []
};

const asyncDataReducer = (state = defaultState, action) => {
  switch(action.type) {
    case REQUESTING_DATA:
      return {
        fetching: true,
        users: []
      }
    case RECEIVED_DATA:
      return {
        fetching: false,
        users: action.users
      }
    default:
      return state;
  }
};

const store = Redux.createStore(
  asyncDataReducer,
  Redux.applyMiddleware(ReduxThunk.default)
);

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36.

Link to the challenge:

You’re close to the solution (the instructions could be a bit clearer on this one).

Instead of calling store.dispatch in your handleAsync, remember that dispatch was passed in as a callback. You invoke it directly. And, to pass the tests you’ll need to invoke receivedData with the data value that was created inside the setTimeout.

Final code for handleAsync looks like:

const handleAsync = () => {
  return function(dispatch) {
    // dispatch request action here
    dispatch(requestingData());
    setTimeout(function() {
      let data = {
        users: ['Jeff', 'William', 'Alice']
      }
      // dispatch received data action here
      dispatch(receivedData(data));
    }, 2500);
  }
};
1 Like

Your understanding seems pretty spot on, so I’ll just clarify a few points.

If I know I need to request data from, say, a server, and I know this is going to take time, then I don’t want to immediately update the Redux store because this will cause an error (updating with no data).

Not that the function will necessarily take a long time, but rather that it is an async function. This means anytime you’re doing I/O, waiting on promises, setTimeout, etc.

This is because of how the event loop works. When the javascript engine sees an async function, it will add it to the task queue and run it on the next tick, but will continue to process the next synchronous functions until completion before it completes the current tick.

That’s why you don’t have the data yet – because the function won’t run until it’s callback is fired in a future tick of the loop.

And if for some reason you made a long running synch call, like fs.readfilesync, then the function will run to completion, therefore blocking everything until then.

The main takeaway is that javascript is a run to completion language, unless told to put tasks into the queue for future processing.

Therefore, I wouldn’t call requestingData() anywhere in the code except inside the thunk?

That’s just a best practice. You can actually make the request in the UI, and dispatch when you receive the data. But redux encourages separating the UI from logic, so therefore you make the async call in the thunk.

One thing to keep in mind is that thunks are just really curried functions, which allows you to delay evaluation until a future call.

handleAsynch()(incoming dispatch); → this looks like a curried function, so it takes in a dispatch as an argument, right?

Yes. The middleware will inject dispatch and getState into the curried function so you can have access to the store as well.

This will be useful if you ever need to access state from a different reducer in your action creator, like

const onlyDispatchWhenX = () => {
  return function(dispatch, getState) {
    // decide whether to dispatch depending on state
    const shouldDispatch = getState().shouldDispatch

    if(shouldDispatch) dispatch(someAction())
  }
};

And since javascript runs to completion, this thunk will complete in the current tick and your reducer will get the updated data.

If I haven’t answered your question or you would like some other clarification, just let me know.

1 Like

Thank you for pointing that out! No need to call the store.dispatch directly if it’s already passed in.

Thank you for explaining the concept of async. I’ve run into it several more times in the FFC curriculum, and now I have a better grasp of what it means.

1 Like