3

I have the following useEffect hook in my functional component that I only want to use once (when the component mounts) in order to load some data from an API:

const Gear = () => {
  const [weaponOptions, setWeaponOptions] = useState([
    {
      key: "0",
      label: "",
      value: "null"
    }
  ]);
  const [weapon, setWeapon] = useState("null");

  useEffect(() => {
    console.log("Gear.tsx useEffect");

    const fetchWeaponsOptions = async (): Promise<void> => {
      const weaponsData = await getWeapons();
      const newWeaponOptions: DropdownOptionType[] = [
        ...weaponOptions,
        ...weaponsData.map(({ id, name }) => {
          return {
            key: id,
            label: name,
            value: id
          };
        })
      ];
      setWeaponOptions(newWeaponOptions);
    };

    fetchWeaponsOptions();
  }, []);

  // TODO add weapon dropdown on change, selected weapon state
  const handleWeaponChange = ({ value }: DropdownOptionType): void => {
    setWeapon(value);
  };

  return (
    <div>
      <h2>Gear:</h2>
      <Dropdown
        defaultValue={weapon}
        label="Weapon"
        name="weapon"
        options={weaponOptions}
        onChange={handleWeaponChange}
      />
    </div>
  );
};

A React documentation note states that this is valid practice when you only want the effect to run on mount an unmount:

If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.

But I am getting the following create-react-app linter warning:

35:6 warning React Hook useEffect has a missing dependency: 'weaponOptions'. Either include it or remove the dependency array react-hooks/exhaustive-deps

The useEffect triggers if the weaponOptions array changes if I pass it as a dependency, resulting in an endless loop because the hook itself changes the weaponOptions state. The same thing happens if I omit the empty array argument.

What is the correct approach here?

2
  • It will, please show us where does weaponOptions comes from Commented Feb 1, 2020 at 15:22
  • @DennisVash sry, added the whole functional component code to the initial question to make things clearer Commented Feb 1, 2020 at 15:24

2 Answers 2

5

I only want to use once (when the component mounts) in order to load some data from an API

Therefore according to your logic, you don't need to depend on the component's state:

const INITIAL = [
  {
    key: '0',
    label: '',
    value: 'null'
  }
];

const App = () => {
  const [weaponOptions, setWeaponOptions] = useState(INITIAL);

  useEffect(() => {
    const fetchWeaponsOptions = async () => {
      const weaponsData = await getWeapons();
      const weaponsOptions = [
        ...INITIAL, // No state dependency needed
        ...weaponsData.map(({ id, name }) => {
          return {
            key: id,
            label: name,
            value: id
          };
        })
      ];
      setWeaponOptions(weaponsOptions);
    };

  }, []);

  return <></>;
};

But, it is common when you want to use a useEffect once, and it depends on a state, to use a boolean reference like so:

const App = () => {
  const [weaponOptions, setWeaponOptions] = useState(INITIAL);

  const isFirstFetch = useRef(true);

  useEffect(() => {
    const fetchWeaponsOptions = async () => {...}

    if (isFirstFetch.current) {
      fetchWeaponsOptions();
      isFirstFetch.current = false;
    }
  }, [weaponOptions]);

  return <></>;
};
Sign up to request clarification or add additional context in comments.

1 Comment

This makes perfect sense :). Thanks a lot
0

As you can see that is not an error but a warning. React is telling you that you are using weaponOptions inside useEffect but you didn't pass it as a dependency. Again, that is just a warning, you don't have to do it.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.