【问题标题】:useEffect is not running for every change in dependency arrayuseEffect 不会针对依赖数组中的每次更改运行
【发布时间】:2020-03-24 12:30:46
【问题描述】:

当我更新费用时,它不会触发 useEffect 将数据存储在本地存储中。

codesandbox

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

export const ExpenseContext = React.createContext();

const ExpenseState = (props) => {
   const [state, setState] = useState(
      () => JSON.parse(localStorage.getItem('expenses')) || []
);

useEffect(() => {
    console.log('didUpdate', state)
    state.length !== 0 && localStorage.setItem('expenses', JSON.stringify(state));
}, [state]) 

function addExpense({id, name, category, value, note, date}){
    const expense = state.find(exp => exp.id === id);
    if(!expense){
        setState(state => ([...state, {id, name, category, value, note, date}]))
    }else{
        const updatedExpense = state.map(exp => {
                if(exp.id === expense.id){
                    return {
                        ...exp,
                        ...{id, name, category, value, note, date}
                    }
                }else{
                    return exp
                }
            })

        setState(updatedExpense)
    }
    console.log(state)
}



return (
    <ExpenseContext.Provider
        value={{
            expenses: state,
            addExpense: addExpense,
        }}
    >
        {props.children}
    </ExpenseContext.Provider>
)
}

我正在为数组的每个元素调用 addExpense。假设我们有 4 个费用存储在存储中。现在我们更新了一项费用,然后运行下面的代码。

for(const expense of expenses){
        expenseContext.addExpense(expense);
    }

现在只有最后一个元素才会触发使用效果,并且费用也不会在上下文中更新。

【问题讨论】:

  • 请用 runnable minimal reproducible example 更新您的问题,使用 Stack Snippets([&lt;&gt;] 工具栏按钮)演示问题。 Stack Snippets 支持 React,包括 JSX; here's how to do one.
  • 当然,it's documented - 在被链接的页面上 - 它用于延迟初始化状态
  • 谢谢@mfeineis!不过,|| [] 仍然是不必要的,对吧? useState 从不返回虚假值。
  • 不,这也很好,这似乎是通过JSON.parse(localStorage.getItem("expenses")) 找不到数据时的初始情况的后备方案。我把代码放到一个codesandbox中,似乎工作得很好,虽然最好useCallback回调,显然codesandbox.io/s/dark-cherry-06to9
  • @mfeineis - 但是useState 返回一个[state, setter] 数组,所以state 是否虚假并不重要,该数组永远不会。是的,它会工作,因为|| [] 从来没有参与过,所以没关系,但是……(你必须仔细观察——也许你已经有了——但那条线是= useState(/*function*/) || [];)

标签: reactjs react-hooks


【解决方案1】:

您的代码正在运行,我不确定您是否只是错过了一些东西?

1. export default ExpenseState 不见了?

2. ExpenseState 上下文包装您的应用程序

假设您有默认的 CRA 设置:

// index.js
import React from "react"
import ReactDOM from "react-dom"
import ExpenseState from "./AppContext" // or where your Context

import App from "./App"

const rootElement = document.getElementById("root")
ReactDOM.render(
  <ExpenseState>
    <App />
  </ExpenseState>,
  rootElement
)

还有你的 App.js

// App.js
import React, { useContext } from "react"
import { ExpenseContext } from "./AppContext"

export default () => {
  const { expenses, addExpense } = useContext(ExpenseContext)

  const handleExpense = () => {
    addExpense({
      id: 1, // Change ID to add another object
      name: "some name",
      category: "some category", // keep the id and change some values for update
      value: "some value",
      note: "some note",
      date: "some date"
    })
  }

  return (
    <div className="App">
      <button onClick={handleExpense}>Add Expenses</button>
      <pre>
        <code>{JSON.stringify(expenses, null, 2)}</code>
      </pre>
    </div>
  )
}

工作codeSandBox 示例。

【讨论】:

  • 添加也为我工作。我在更新时遇到问题。请检查codeandbox(codesandbox.io/s/bold-hodgkin-bu1oc)
  • 根据代码逻辑,除非id 不同,并且您的setCounter 状态总是产生一个新的id,否则您无法更新。
  • 请立即查看链接
【解决方案2】:

问题在于useState 的工作方式与您想象的不同。在消费者内部,您连续三次同步调用api.addExpense(...),期望调用setState 将就地更新state - 它不会,状态仅在下一次渲染调用时更新,而不是在第 40 行,您的console.log 表明您期待它。

因此,不是您期望的三个完整状态更新(每个 expense 一个),您只能从 {id: "d6109eb5-9d7b-4cd3-8daa-ee485b08361b", name: "book", category: "personal care", value: 200} 的最后一次更新中获得状态,它没有任何变化并且仍然基于 原始状态不是第一项的值已设置为 800 的状态。

我建议修改您的 ExpenseState api 以获取一系列费用,以便它可以适当地处理多个内部更新。


  function addOrUpdateExpense(state, { id, name, category, value }) {
    const expense = state.find(exp => exp.id === id);
    if (!expense) {
      return [...state, { id, name, category, value }];
    } else {
      const updatedExpense = state.map(exp => {
        if (exp.id === expense.id) {
          return {
            ...exp,
            ...{ id, name, category, value }
          };
        } else {
          return exp;
        }
      });

      return updatedExpense;
    }
  }

  function addExpenses(expenses) {
    let newState = [...state];
    for (const expense of expenses) {
      newState = addOrUpdateExpense(newState, expense);
    }
    setState(newState);
  }

  // ...usage

  function doIt() {
    api.addExpenses(expenses);
  }

这是一个常见的误解,请记住下一个状态仅在下一个渲染阶段异步可用,将来自useState 的状态视为在渲染过程中不可变。

【讨论】:

    猜你喜欢
    • 2023-01-26
    • 1970-01-01
    • 2022-01-11
    • 1970-01-01
    • 2023-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多