从子组件内的useEffect钩子调度数据时,UseReducer被调用两次



我正在ReactJS中制作待办事项/购物清单。除了能够通过输入手动向列表中添加项目外,用户还应该能够以编程方式添加项目。

我使用createContext()useReducer来管理state()

当我通过提供一个通过道具的数组以编程方式添加项目并侦听useEffect中的更改时,useEffectdispatch会激发两次,尽管我只更改了一次道具。

然而,当我第一次通过道具提供物品阵列时,这种情况并没有发生。

因此,在第一次之后,当dispatch激发两次时,列表会获得重复项(也是重复密钥(。

这是由于我不知道的重新渲染过程吗?任何帮助都是非常感谢的,因为我真的陷入了困境。

这是代码:

包含useEffect的上下文提供程序组件,该组件在道具更改时触发useReducer的调度方法:

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>
);
}

我的reducer函数(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;

生成ingredientArray的父组件。它获取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

最新更新