4

I have a React functional component with two state variables (itemsData & itemsCollections). The variables are updated in the useEffect method. But after useEffect occur one of the state variables is null.

Upon switching the setStateFunctions (setItemsData & setItemsCollect) call order both arguments are inialized as expected.

How's that?

const MyComponent = ({itemsIds}) => {
   const [itemsData, setItemsData] = useState([]);
   const [itemsCollections, setItemsCollect] = useState({});

 useEffect(() => {
        fetchItemsData({ itemsIds }).then(({ items, itemCollect }) => {
             setItemsData(items);
             setItemsCollect(itemCollect);
        })
    }, [itemsIds]);
...
console.log('itemsData', itemsData) // the expected array
console.log('itemCollect', itemCollect) // empty objecy

State after useEffect: itemCollect = {}, itemsData = [{value:...},...]

Switching the order of the calls:

const MyComponent = ({itemsIds}) => {
   ...
 useEffect(() => {
        fetchItemsData({ itemsIds }).then(({ items, itemCollect }) => {
             setItemsCollect(itemCollect); // <--> switched rows
             setItemsData(items); // <--> switched rows

        })
    }, [itemsIds]);
...
console.log('itemsData', itemsData) // the expected array
console.log('itemCollect', itemCollect) // the expected object

State after useEffect: itemCollect = { someValue: ...} , itemsData = [{value:...},...]

  • Try logging to console inside of useEffect but outside of fetchItemsData. As far as I know the order should not matter – Matt Oestreich Jul 14 '19 at 15:21
  • Possible duplicate of [Does React batch state update functions when using hooks?](https://stackoverflow.com/questions/53048495/does-react-batch-state-update-functions-when-using-hooks) – Aprillion Jul 14 '19 at 15:32

1 Answers1

2

There is a performance optimization called batching, which can change between React versions. When this optimization is applied, multiple setState calls will be batched together before the next render (and the order does not matter).

When not applied (e.g. inside a Promise as in your case, see Does React batch state update functions when using hooks?), then each state update will trigger a new render (and the order matters).

=> console.log('itemCollect', itemCollect) may log different data in each render.

If you need to force a single state update, then calling a single dispatch from useReducer might be the best option.

Aprillion
  • 21,510
  • 5
  • 55
  • 89
  • Thanks, your answer explains part of the behavior but the missing part is why changing the rows changed the result. So, I've continued to investigate the differences between the calls and it appears that in the first case render is triggered twice, but when I switched the rows, render was triggered only once. Meaning the assumption of batching is not implemented in promises is wrong. I guess the reason for triggering once or twice is the size of the variables. 'items' is a big array with many elements and 'itemCollect' is a small object with one key. – Shira Navama Jul 15 '19 at 14:50
  • If you will update your answer to be more precise and answering the main question ( meaning - yes different call orders can change the number of triggered renders) I'll happily accept it – – Shira Navama Jul 15 '19 at 15:05
  • I cannot verify the method how you detected number of renders and I don't have other sources to believe the number of renders can depend on the order of function calls within a single stack frame. I might check React source code, but until then I won't be able to update my answer with any more proven facts. Feel free to post your own answer with your findings though. – Aprillion Jul 15 '19 at 17:44