javascript - Context Api 状态没有改变

标签 javascript arrays reactjs frontend react-context

我试图从使用 useContext 钩子(Hook)消耗上下文的组件中调用 Context Provider 中名为 deleteTask 的函数,该函数会从上下文提供程序状态下的数组中删除某个项目,但是当我这样做时,提供程序的状态根本没有改变,我尝试跟踪问题并且函数执行,但看起来它是否在复制的提供程序的范围内执行?还尝试了添加任务的功能,我遇到了同样的问题。我还添加了一个设置事件任务的功能,但我不知道为什么这个功能有效,而其他功能则不起作用。我真的不知道发生了什么,这是代码,请帮助我:

tasks-context.jsx

import React, { useState } from 'react';
import { useEffect } from 'react';

const dummyTasks = [{
  task: {
    text: 'hello',
  },
  key: 0,
  isActive: false
},
{
  task: {
    text: 'hello 2',
  },
  key: 1,
  isActive: false
}];

export const TasksContext = React.createContext({ });

export const TasksProvider = ( props ) => {
  const [ tasks, setTasks ] = useState( dummyTasks );
  const [ activeTask, setActiveTask ] = useState();

    
  //NOT WORKING
  const deleteTask = ( taskToDeleteKey ) =>{
    setActiveTask( null );
    setTasks( tasks.filter( task => task.key !== taskToDeleteKey ));
  };

  //THIS ONE WORKS (??)
  const handleSelectTask = ( taskToSelect, key ) =>{
    setActiveTask( taskToSelect );
    const newTaskArray = tasks.map( task => {
      if( task.key === key ){
        task.isActive = true;
      }else{
        ficha.isActive = false;
      }
      return task;
    });
    setTask( newTaskArray );
  };

  return ( <TasksContext.Provider 
      value={{ tasks,
               activeTask,
               addTask,
               deleteTask,
               handleSelectTask}}>
    {props.children}
  </TasksContext.Provider>
  );
};

“主要”

Main.jsx

import React from 'react';
import './assets/styles/gestion-style.css';
import './assets/styles/icons.css';

import { TasksProvider } from '../../Context/tasks-context';

import TaskContainer from './components/taskContainer.jsx';

function Main( props ) {
  
 return (
      <TasksProvider>
        <TaskContainer />
      </TasksProvider>
    );
}

任务容器映射任务数组:

TaskContainer.jsx

import React, { useContext, useEffect } from 'react';
import TaskTab from './TaskTab';


import { TasksContext } from '../../Context/tasks-context';

function TaskContainer( props ) {
  const { tasks } = useContext( TasksContext );

  return (
    <div className="boxes" style={{ maxWidth: '100%', overflow: 'hidden' }}>
      {tasks? tasks.map( taskTab=>
        ( <TaskTab task={taskTab.task} isActive={taskTab.isActive} key={taskTab.key} taskTabKey={taskTab.key} /> ))
        :
        null
      }
    </div>
  );
}

export default TaskContainer;

以及我从中调用上下文函数来删除的任务组件:

TaskTab.jsx

import React, { useContext } from 'react';

import { TasksContext } from '../../Context/tasks-context';

function TaskTab( props ) {
  let { task, isActive, taskTabKey } = props;
  const { handleSelectTask, deleteTask } = useContext( TasksContext );

  const selectTask = ()=>{
    handleSelectTask( task, taskTabKey );
  };

  const handleDelete = () =>{
    deleteTask( taskTabKey );
  };

  return (
    <div onClick={ selectTask }>
      <article className={`${task.type} ${isActive ? 'active' : null}`}>
        <p className="user">{task.text}</p>
        <button onClick={handleDelete}>
          <i className="icon-close"></i>
        </button>
      </article>
    </div>
  );
}

export default TaskTab;

最佳答案

感谢您提出这个好问题!

这里发生的事情令人困惑,这是可以理解的,我自己花了一段时间才意识到这一点。

TL;博士:handleSelectTask每次单击deleteTask按钮时都会调用提供程序中的因为事件传播。 handleSelectTask未使用 deleteTask 已修改的状态,即使它在它后面运行,因为它已经关闭了初始 tasks数组。


快速解决方案 1

阻止事件从删除按钮单击传播到 TaskTab div 单击,这可能是所需的行为。

// in TaskTab.jsx
const handleDelete = (event) => {
  event.stopPropagation(); // stops event from "bubbling" up the tree
  deleteTask(taskTabKey);
}

在 DOM 中(也由 React 模拟),事件在树中“冒泡”,以便父节点可以处理来自子节点的事件。在示例中,<button onClick={handleDelete}><div onClick={selectTask}> 的子项,这意味着当按钮触发单击事件时,它将首先调用handleDelete像我们想要的那样运行,但它也会调用 selectTask之后从父 div 中调用函数,这可能是无意的。您可以在MDN上阅读有关事件传播的更多信息。 .


快速解决方案 2

编写状态更新以在调用它们时使用中间状态值。

// in tasks-context.jsx
const deleteTask = ( taskToDeleteKey ) => {
  setActiveTask(null);
  // use the function version of setting state to read the current value whenever it is run
  setTasks((stateTasks) => stateTasks.filter(task => task.key !== taskToDeleteKey));
}

const handleSelectTask = ( taskToSelect, key ) =>{
  setActiveTask( taskToSelect );
  // updated to use the callback version of the state update
  setTasks((stateTasks) => stateTasks.map( task => {
    // set the correct one to active
  }));
};

使用 setTasks 的回调版本状态更新,它实际上会读取应用更新时的值(包括并且特别是在更新中间!),因为 handleSelectTask之后调用,意味着它实际上看到了已经被 deleteTask 修改过的数组。最先跑的!您可以在 React 文档 (hooks) 中阅读有关设置状态的回调变体的更多信息。 (setState) 。请注意,此“修复”意味着您的组件仍将调用 handleSelectTask即使任务已被删除。它不会有任何不良影响,只是要注意。


让我们更详细地了解一下正在发生的事情:

首先,tasks变量是从 useState 创建的。整个组件都使用相同的变量,这是完全正常的。

// created here
const [ tasks, setTasks ] = useState( dummyTasks );
const [ activeTask, setActiveTask ] = useState();

const deleteTask = ( taskToDeleteKey ) =>{
  setActiveTask( null );
  // referenced here, no big deal
  setTasks( tasks.filter( task => task.key !== taskToDeleteKey ));
};

const handleSelectTask = ( taskToSelect, key ) =>{
  setActiveTask( taskToSelect );
  // tasks is referenced here, too, awesome
  const newTaskArray = tasks.map( task => {
    if( task.key === key ){
      task.isActive = true;
    }else{
      task.isActive = false;
    }
    return task;
  });
  setTasks( newTaskArray );
};

问题在于,如果两个函数都尝试在同一渲染周期中更新相同的状态值,则它们都将引用任务数组的原始值,即使另一个函数已经尝试过更新状态值!在你的情况下,因为 handleSelectTask正在之后 deleteTask 运行,这意味着handleSelectTask将使用尚未修改的数组更新状态!当它运行时,它仍然会在数组中看到两个项目,因为 tasks在实际提交更新并且所有内容重新渲染之前,变量不会更改。这使得删除部分看起来不起作用,而实际上它的效果只是从 handleSelectTask 开始被丢弃。不知道删除发生在它之前。

关于javascript - Context Api 状态没有改变,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63496992/

相关文章:

javascript - store.sync失败后如何回调?

javascript - 如何在javascript中建立计数和说出问题

php - 如何制作一个php数组

javascript - React 路由器不显示浏览器历史记录

angular - Angular 2 Data Flow 和 Flux 之间的关键区别是什么?

knockout.js - 为什么在 knockout.js 示例中,viewmodel 有时被定义为一个函数,有时又被定义为一个直接变量定义?

javascript - 如何使用php清除和填充MySQL表

c - 从文件动态分配二维 double 组

arrays - 如何使用openmp并行化数组元素的移动

laravel - 如何在不使用路由的情况下管理 React Js 到多页面应用程序?