2
votes

I have a more complex version of the following pseudo-code. It's a React component that, in the render method, tries to get a piece of data it needs to render from a client-side read-through cache layer. If the data is present, it uses it. Otherwise, the caching layer fetches it over an API call and updates the Redux state by firing several actions (which theoretically eventually cause the component to rerender with the new data).

The problem is that for some reason it seems like after dispatching action 1, control flow moves to the top of the render function again (starting a new execution) and only way later continues to dispatch action 2. Then I again go to the top of the render, and after a while I get action 3 dispatched.

I want all the actions to fire before redux handles the rerender of the component. I would have thought dispatching an action updated the store but only forced components to update after the equivalent of a setTimeout (so at the end of the event loop), no? Is it instead the case that when you dispatch an action the component is updated synchronously immediately, before the rest of the function where the dispatch happens is executed?

class MyComponent {
  render() {
    const someDataINeed = CachingProvider.get(someId);

    return (
      <div>{someDataINeed == null ? "Loading" : someDataINeed }</div>
    );
  }
}

class CachingProvider {
  get(id) {
    if(reduxStoreFieldHasId(id)) {
      return storeField[id];
    }
    
    store.dispatch(setLoadingStateForId(id));
    
    Api.fetch().then(() => {
      store.dispatch(action1);
      store.dispatch(action2);
      store.dispatch(action3);
    });

    return null;
  }
}
2

2 Answers

1
votes

In addition to @TrinTragula's very important answer:

This is React behaviour. Things that trigger rerenders that are invoked synchronously from an effect/lifecycle or event handler are batched, but stuff that is invoked asnychronously (see the .then in your code) will trigger a full rerender without any batching on each of those actions.

The same behaviour would apply if you would call this.setState three times in a row.

You can optimize that part by adding batch which is exported from react-redux:


    Api.fetch().then(() => {
      batch(() => {
        store.dispatch(action1);
        store.dispatch(action2);
        store.dispatch(action3);
      })
    });
1
votes

You should never invoke heavy operations inside of a render function, since it's going to be triggered way more than you would like to, slowing down your app.

You could for example try to use the useEffect hook, so that your function will be executed only when your id changes.

Example code:

function MyComponent {
  useEffect(() => {
    // call your method and get the result in your state
  }, [someId]);

  return (
    <div>{someDataINeed == null ? "Loading" : someDataINeed }</div>
  );

}