簡體   English   中英

當從子組件中的 useEffect 鈎子分派數據時,會調用 UseReducer 兩次

[英]UseReducer is called twice when dispatching data from useEffect hook within child component

我正在 ReactJS 中制作待辦事項/購物清單。 除了能夠通過輸入手動將項目添加到列表中,用戶還應該能夠以編程方式添加項目。

我正在使用createContext()useReducer來管理state()

當我通過 props 提供一個數組以編程方式添加項目並監聽useEffect更改時,盡管我只更改了一次useEffect ,但useEffectdispatch會觸發兩次。

但是,當我第一次通過 props 提供項目數組時,這不會發生。

因此,在第一次之后,當dispatch觸發兩次時,列表會得到重復(也是重復的鍵)。

是否由於某些我不知道的重新渲染過程而發生? 任何幫助都非常感謝,因為我真的堅持這個。

這是代碼:

包含 useEffect 的上下文提供程序組件,該組件在 props 更改時從 useReducer 觸發 dispatch 方法:

import React, { createContext, useEffect, useReducer } from 'react';
import todosReducer from '../reducers/todos.reducer';
import { ADD_INGREDIENT_ARRAY } from '../constants/actions';

const defaultItems = [
  { id: '0', task: 'Item1', completed: false },
  { id: '1', task: 'Item2', completed: false },
  { id: '2', task: 'Item3', completed: false }
];

export const TodosContext = createContext();
export const DispatchContext = createContext();

export function TodosProvider(props) {
  const [todos, dispatch] = useReducer(todosReducer, defaultItems)

  useEffect(() => {
    if (props.ingredientArray.length) {
      dispatch({ type: ADD_INGREDIENT_ARRAY, task: props.ingredientArray });
    }
  }, [props.ingredientArray])

  return (
    <TodosContext.Provider value={todos}>
      <DispatchContext.Provider value={dispatch}>
        {props.children}
      </DispatchContext.Provider>
    </TodosContext.Provider>
  );
}

我的減速器函數( ADD_INGREDIENT_ARRAY是從上面的代碼片段中調用的函數):

import uuidv4 from "uuid/dist/v4";
import { useReducer } from "react";
import {
  ADD_TODO,
  REMOVE_TODO,
  TOGGLE_TODO,
  EDIT_TODO,
  ADD_INGREDIENT_ARRAY
} from '../constants/actions';

const reducer = (state, action) => {
  switch (action.type) {
    case ADD_TODO:
      return [{ id: uuidv4(), task: action.task, completed: false }, ...state];
    case REMOVE_TODO:
      return state.filter(todo => todo.id !== action.id);
    case TOGGLE_TODO:
      return state.map(todo =>
        todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
      );
    case EDIT_TODO:
      return state.map(todo =>
        todo.id === action.id ? { ...todo, task: action.task } : todo
      );
    case ADD_INGREDIENT_ARRAY: 
        console.log('THE REDUCER WAS CALLED')
        return [...action.task.map(ingr => ({ id: uuidv4(), task: ingr.name, completed: false }) ), ...state]
    default:
      return state;
  }
};

export default reducer;

呈現每個項目並使用來自上述代碼片段的上下文的列表組件:

import React, { useContext, useEffect, useState } from 'react';
import { TodosContext, DispatchContext } from '../contexts/todos.context';
import Todo from './Todo';

function TodoList() {
  const todos = useContext(TodosContext);

  return (
    <ul style={{ paddingLeft: 10, width: "95%" }}>
      {todos.map(todo => (
        <Todo key={Math.random()} {...todo} />
      ))}
    </ul>
  );
}

export default TodoList;

包含包含在傳遞道具的上下文提供程序中的列表的應用程序組件:

import React, { useEffect, useReducer } from 'react';
import { TodosProvider } from '../contexts/todos.context';
import TodoForm from './TodoForm';
import TodoList from './TodoList';

function TodoApp({ ingredientArray }) {
  return (
    <TodosProvider ingredientArray={ingredientArray}>
      <TodoForm/>
      <TodoList/>
    </TodosProvider>
  );
}

export default TodoApp;

以及傳遞道具的頂級組件:

import React, { useEffect, useContext } from 'react';
import TodoApp from './TodoApp';
import useStyles from '../styles/AppStyles';
import Paper from '@material-ui/core/Paper';

function App({ ingredientArray }) {
  const classes = useStyles();  

  return (
    <Paper className={classes.paper} elevation={3}>
      <div className={classes.App}>
        <header className={classes.header}>
          <h1>
            Shoppinglist
        </h1>
        </header>
        <TodoApp ingredientArray={ingredientArray} />
      </div>
    </Paper>
  );
}

export default App;

制作成分數組的父組件。 它獲取 state.recipes 數組中的最后一個配方並將其作為道具傳遞給shoppingList:

...
  const handleSetNewRecipe = (recipe) => {
    recipe.date = state.date;
    setState({ ...state, recipes: [...state.recipes, recipe] })
  }
...

 {recipesOpen ? <RecipeDialog
    visible={recipesOpen}
    setVisible={setRecipesOpen}
    chosenRecipe={handleSetNewRecipe}
  /> : null}

...

 <Grid item className={classes.textAreaGrid}>
     <ShoppingList ingredientArray={state.recipes.length ? state.recipes.reverse()[0].ingredients : []}/>
 </Grid>

....

我究竟做錯了什么?

很高興我們得到了這個排序。 根據主帖的評論,直接改變 React 狀態而不是通過 setter 函數更新它會導致狀態的實際值與依賴組件和樹下的效果不同步。

我仍然不能完全解釋為什么在這種情況下它會導致您的特定問題,但無論如何,刪除對reverse的可變調用並用這個簡單的索引計算替換它似乎已經解決了這個問題:

state.recipies[state.recipies.length-1].ingredients

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM