useEffect не выполняется для каждого изменения в массиве зависимостей - PullRequest
0 голосов
/ 24 марта 2020

Когда я обновляю расход, он не запускает 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);
    }

Теперь эффект использования срабатывает только для последнего элемента, а расходы также не обновляются в контексте.

Ответы [ 2 ]

0 голосов
/ 25 марта 2020

Проблема в том, что 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 как неизменяемое в ходе рендеринга.

0 голосов
/ 24 марта 2020

Ваш код работает, я не уверен, что вы просто пропустили некоторые вещи?

1. export default ExpenseState отсутствует?

2. Оберните ваше приложение с ExpenseState context

Предполагая, что у вас есть настройки по умолчанию 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
)

И ваше приложение. 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)}
      
)}

Рабочий codeSandBox пример.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...