繁体   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