Home > Mobile >  Why is React calling some of my App's functions multiple times?
Why is React calling some of my App's functions multiple times?

Time:09-13

I was debugging a React app and noticed that some of my functions were called multiple times in a way I could not explain.

I initially thought it was some sort of "developer feature" and tried to run a build, and all I could see if that the APIs that should not be called were called once instead of twice:

import { useCallback, useState } from "react";

function App() {
  const cities = ["montreal", "london", "shanghai"];

  const [city, setCity] = useState(cities[0]);

  const getCityParameter = useCallback(
    (newCity) => {
      console.log("[1] getCityParameter");
      console.log(`newCity: ${newCity}`);
      console.log(`city: ${city}`);
      return (newCity ?? city).toUpperCase();
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [city]
  );
  const [cityParameter, setCityParameter] = useState(getCityParameter());

  const handleChange = useCallback(
    (event) => {
      const newCity = event?.target.value;
      console.log("handleCityChange");
      console.log(`newCity: ${newCity}`);
      if (newCity !== undefined) {
        setCity(newCity);
      }
      const newCityParameter = getCityParameter(newCity);
      setCityParameter(newCityParameter);
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [city]
  );

  return (
    <>
      <select onChange={handleChange} value={city}>
        {cities.map((city) => {
          return (
            <option value={city} key={city}>
              {city}
            </option>
          );
        })}
      </select>
      <div>{cityParameter}</div>
    </>
  );
}

export default App;

I created this code sandbox here: https://codesandbox.io/s/restless-butterfly-brh7fk?file=/src/App.js

If you clear the console log, and change the dropdown, you will notice that getCityParameter is called 3 times when I would expect it to be called once.

This seems to be a pretty low-level React feature and I apologize for the "not-so-small" example - this is the best I could come up with to reproduce the behavior.

Can anyone explain?

CodePudding user response:

In the change handler, first:

const newCityParameter = getCityParameter(newCity);

So that's one call for getCityParameter. Then, the component re-renders because the state setter was called. This:

const [cityParameter, setCityParameter] = useState(getCityParameter());

is like doing

const result = getCityParameter();
const [cityParameter, setCityParameter] = useState(result);

The function gets called again every time the component renders, so you see it again. Finally, because you're in strict mode:

root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

The app re-renders a second time, so getCityParameter runs again, making a total of 3 times that it's been called when the dropdown is changed.

The initial state value is only used when the component mounts, of course - which means that calling a function every time the component renders when not needed might be seen as unnecessary or confusing. If you wanted getCityParameter to not be called on re-renders, and to only be called on mount in order to determine the initial state value, use the functional version of useState. Change

const [cityParameter, setCityParameter] = useState(getCityParameter());

to

const [cityParameter, setCityParameter] = useState(getCityParameter);
  • Related