skip to Main Content

A component in my NextJS 13 project has two useEffect hooks, one that sets a React Context state array variable called singleRecipe, and another that I would like to set another local state variable called ingredientsArr. The issue arises on first render, I am receiving undefined for the singleRecipe state variable when my code reaches the second useEffect. How would I go about updating the ingredientsArr variable to an element from the singleRecipe only once singleRecipe is defined?

useEffect(() => {
  setSingleRecipe(recipes.find(({id}) => id === Number(recipeID)))
})

useEffect(() => {
  setIngredientsArr(JSON.parse(singleRecipe.ingredients))
}, [singleRecipe])
  • singleRecipe is stored in context state and ingredientsArr is local to this component

3

Answers


  1. I am receiving undefined for the singleRecipe state variable

    You can check if the value is undefined before trying to use it. For example:

    useEffect(() => {
      if (singleRecipe) {
        setIngredientsArr(JSON.parse(singleRecipe.ingredients))
      }
    }, [singleRecipe])
    

    Or perhaps use optional chaining and conditionally set to a blank array if the overall value is "falsy":

    useEffect(() => {
      setIngredientsArr(JSON.parse(singleRecipe?.ingredients ?? '[]'))
    }, [singleRecipe])
    

    Alternatively, if the goal is to perform both of these operations in one overall effect then just use one call to useEffect:

    useEffect(() => {
      const newRecipe = recipes.find(({id}) => id === Number(recipeID));
      setSingleRecipe(newRecipe);
      setIngredientsArr(JSON.parse(newRecipe.ingredients));
    });
    

    It’s worth noting, if only for future readers… This effect will run on every render. Which is probably not what you want. Especially if the effect updates state, which triggers another render.

    You can modify this effect to only run on the first render by providing an empty dependency array:

    useEffect(() => {
      const newRecipe = recipes.find(({id}) => id === Number(recipeID));
      setSingleRecipe(newRecipe);
      setIngredientsArr(JSON.parse(newRecipe.ingredients));
    }, []); // <--- here
    

    Or to run only when recipeID changes:

    useEffect(() => {
      const newRecipe = recipes.find(({id}) => id === Number(recipeID));
      setSingleRecipe(newRecipe);
      setIngredientsArr(JSON.parse(newRecipe.ingredients));
    }, [recipeID]); // <--- here
    
    Login or Signup to reply.
  2. I think you can achieve this by adding a check to the second use effect:

    useEffect(() => {
      setSingleRecipe(recipes.find(({id}) => id === Number(recipeID)))
    })
    
    useEffect(() => {
      if(!singleRecipe) return;
      setIngredientsArr(JSON.parse(singleRecipe.ingredients))
    }, [singleRecipe])
    

    Although it seems strange that the first useEffect has no dependencies. What is recipeId? If that is a prop/state var you should probably include it in the dep array:

    useEffect(() => {
      setSingleRecipe(recipes.find(({id}) => id === Number(recipeID)))
    }, [recipeId])
    

    If you don’t include it in the dep array, it will set it on every render which isn’t ideal.

    The reason it’s undefined is you are setting it in the first useEffect and state updates asynchronously (so the new value isn’t available until the next render).

    Login or Signup to reply.
  3. you don’t really need two useEffect here:

    useEffect(() => {
      const myRecipe = recipes.find(({id}) => id === Number(recipeID));
      setSingleRecipe(myRecipe);
      setIngredientsArr(JSON.parse(myRecipe.ingredients));
    }, []); // empty dependency array to run onlu once after the first render
    

    Note that in your code the first useEffect will run on each component render because it does not have a dependency array.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search