Michał Czapliński
Michał Czapliński

Reputation: 1342

Does a react component using useState hook rerender every `setState` call?

https://codesandbox.io/s/mow5zl5729

import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import axios from "axios";

function useLoading() {
  const [isLoading, setLoading] = React.useState(true);
  const [hasError, setError] = React.useState(false);

  const loadStuff = aPromise => {
    return aPromise
      .then(() => {
        setLoading(false);
      })
      .catch(() => {
        setLoading(false);
        setError(true);
      });
  };

  return { isLoading, hasError, loadStuff };
}

function App() {
  const { isLoading, hasError, loadStuff } = useLoading();

  useEffect(() => {
    loadStuff(axios.get(`https://google.com`));
  }, []);

  console.log(isLoading, hasError);

  return <div />;
}

This is a simplified example of what I mean.

If the promise inside useLoading is rejected, I would have expected the component to render on mount and then render the second time when the error is caught. So, I expect a total of 2 renders with the following state:

1st render:

2nd render:

Instead, it seems that the component rerenders once after setLoading(false) and again after setError(true). So, I get this:

1st render:

2nd render: ( why? )

3rd render:

I suspect that the issue is somehow my use of the promise inside useEffect but I'm not sure where my mental model went wrong.

EDIT:

When I change useLoading to only contain 1 useState, the problem goes away.

broken:

const [isLoading, setLoading] = React.useState(true);
const [hasError, setError] = React.useState(false);

works:

const [loadingState, setLoadingState] = React.useState({
  loading: true,
  error: false
});

Upvotes: 2

Views: 2562

Answers (1)

Sushanth --
Sushanth --

Reputation: 55740

Looks like this has something to do with batching of the state updates. As far as I know React based events will trigger the batch updates but not something that gets triggered outside it. promise in this case.

Because the state calls are not batched, you see the 2nd render where both are set to false

Upvotes: 2

Related Questions