Gaoxin Huang
Gaoxin Huang

Reputation: 198

useEffect break array of useState at first time

I am learning react hooks. I am having mock data js call "MockFireBase.js" as below:

const userIngredientsList = [];
export const Get = () => {
    return userIngredientsList;
}
export const Post = (ingredient) => {
    ingredient.id = userIngredientsList.length + 1;
    userIngredientsList.push(ingredient);
    return ingredient;
}

Then my react hooks component "Ingredients.js" will call this mock utilities as following details:

const Ingredients = () => {
  const [userIngredients, setUserIngredients] = useState([]);
  
  // only load one time
  useEffect(() => { setUserIngredients(Get()); }, []);
  
  const addIngredienHandler = ingredient => {
    let responsData = Post(ingredient);
    setUserIngredients(preIngredients => {
      return [...preIngredients, responsData]
    });
  }
  
  return (
    <div className="App">
      <IngredientForm onAddIngredient={addIngredienHandler} />
      <section>
        <IngredientList ingredients={userIngredients} />
      </section>
    </div>
  );
  )
} 

When I added first ingredient, it added two (of course I get same key issue in console.log). Then I added second ingredient is fine.

If I remove the useEffect code as below, it will work good.

// only load one time
useEffect(() => { setUserIngredients(loadedIngredients); }, []);

I am wondering what I did anything wrong above, if I use useEffect

Upvotes: 0

Views: 483

Answers (1)

amakhrov
amakhrov

Reputation: 3939

The problem is not in useEffect. It's about mutating a global userIngredientsList array.

  1. from useEffect you set initial component state to be userIngredientsList.
  2. Then inside addIngredienHandler you call Post(). This function does two things: 2a. pushes the new ingredient to the global userIngredientsList array`. Since it's the same instance as you saved in your state in step 1, your state now contains this ingredient already. 2a. Returns this ingredient
  3. Then, addIngredienHandler adds this ingredient to the state again - so you end up having it in the state twice.

Fix 1 Remove userIngredientsList.push(ingredient); line from your Post function.

Fix 2 Or, if you need this global list of ingredients for further usage, you should make sure you don't store it in your component state directly, and instead create a shallow copy in your state:

useEffect(() => { setUserIngredients([...Get()]); }, []);

Upvotes: 2

Related Questions