Реагируйте на замену useState на useReducer, одновременно используя настраиваемый хук

поэтому я делаю приложение для отслеживания бюджета, в котором пользователь может добавлять свои источники дохода в список доходов и расходы в список расходов, и у меня оно работает, но я хотел посмотреть, смогу ли я использовать useReducer вместо использования useState столько раз . Вот где я застрял, так как не уверен, что возвращать в редукторе.

Использую 2 гособъекта доходов и расходов. В основном на данный момент я хочу использовать редуктор, чтобы пользователь мог добавлять источник дохода к объекту доходов. Я хочу посмотреть, могу ли я установить объект доходов внутри редуктора, и когда диспетчеризация вызывается с действием, установленным на ADD_INCOME_ITEM, для параметра budgetObj.type будет установлено значение +, и будет вызываться setIncomes (yields.concat (budgetObj)) ( источник дохода будет добавлен в список доходов). Надеюсь, я прояснил это!

App.js:

import React, { useState, useReducer } from 'react';
import './App.css';
import BudgetInput from './components/input/BudgetInput';
import BudgetOutput from './components/output/BudgetOutput';
import IncomeOutputList from './components/output/IncomeOutputList';
import ExpenseOutputList from './components/output/ExpenseOutputList';
    
// custom hook
const useSemiPersistentState = (key, initialState) => {
  const [value, setValue] = React.useState(
    localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key)) : initialState
  );
  
  React.useEffect(()=>{
    localStorage.setItem(key, JSON.stringify(value));
  }, [value, key])

  return [value, setValue];
};

const App = () => {

  // want to replace these 5 lines with useReducer
  const [incomes, setIncomes] = useSemiPersistentState('income',[{}]);
  const [expenses, setExpenses] = useSemiPersistentState('expense',[{}]);
  const [description, setDescription] = useState('');
  const [type, setType] = useState('+');
  const [value, setValue] = useState('');

  const budgetObj = {
    desc: description,
    budgetType: type,
    incomeValue: value
  }

  const initialbudget = {
    desc: '',
    budgetType: '+',
    incomeValue: ''
  }

  const budgetReducer = (state, action) => {
    switch(action.type) {
      case 'ADD_INCOME_ITEM': //want to set the incomes object here
        return setIncomes(incomes.concat(budgetObj)); // not sure if this is correct??
        // also set state here???
    }
    //will add more cases here
  }
  

  const [budget, dispatchBudget] = useReducer( //reducer, initial state
    budgetReducer,
    initialbudget
  );
  
  
  const handleBudgetObjArray = () => {

    if(budgetObj.budgetType === '+') {
      setIncomes(incomes.concat(budgetObj)); //want to move this to reducer
    }
    else if(budgetObj.budgetType === '-') {
      setExpenses(expenses.concat(budgetObj)); //want to move this to reducer
    }

  }

  const handleChange = (event) => {  
    setDescription(event.target.value);
  }

  const handleSelectChange = (event) => { 
    setType(event.target.value);
  }

  const handleValueChange = (event) => {
    setValue(event.target.value);
    console.log(budgetObj)
  }

  const removeInc = (index) => {
     let items = JSON.parse(localStorage.getItem("income"));
     items.splice(index, 1);
     setIncomes(items);
  }

  const removeExp = (index) => {
    let items = JSON.parse(localStorage.getItem("expense"));
    items.splice(index, 1);
    setExpenses(items);
 }

  return (
    <div className="App">
<link href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css" rel="stylesheet" type="text/css"></link>
      <div className="top">
        <BudgetOutput />
        
      </div>

      <div className="bottom">
        <BudgetInput 
          descValue={description}
          onDescChange={handleChange}
          onSelectChange={handleSelectChange}
          type={type}
          onBudgetSubmit={handleBudgetObjArray}
          budgetValue={value}
          onValChange={handleValueChange}
        />

        <div className="container clearfix">
          <IncomeOutputList 
            list={incomes}
            removeIncome={(index)=>removeInc(index)}
          /> 
          <ExpenseOutputList
            list={expenses}
            removeExpense={(index)=>removeExp(index)}
          />
          
        </div>
        
      </div>

    </div>
  )
};


export default App;

В этом файле установлен budgetObj:

import React from 'react';
import IncomeOutput from './IncomeOutput';


// list will be list of income objects
const IncomeOutputList = ({ list, removeIncome }) => {

    return (
        <div className="income__list">
            <div className="income__list--title">INCOME</div>
            {list.map((item, index, arr) => <IncomeOutput 
                                id={item.id} 
                                value={item.incomeValue} 
                                type={item.budgetType} 
                                desc={item.desc} 
                               // handleButton={handler(index)} 
                                handleButton={()=>removeIncome(index)}
                                />
            )}
        </div>
    )
}

export default IncomeOutputList;

person alti21    schedule 05.02.2021    source источник


Ответы (1)


useReducer заменяет useState. Это ваш штат. Так что здесь нет смысла.

case 'ADD_INCOME_ITEM': //want to set the incomes object here
    return setIncomes(incomes.concat(budgetObj)); // not sure if this is correct??

Эти пять useState строк вашего кода, которые включают incomes и setIncomes, будут полностью удалены, поэтому вы не можете использовать их в своем редукторе.

Похоже, что initialState для вашего редуктора - всего лишь один объект бюджета. Это должен быть объект, представляющий все состояние компонента. Что-то вроде этого:

const initialBudget = {
  description: '',
  type: '+',
  value: '',
};

const initialState = {
  incomes: [{}],
  expenses: [{}],
  budgetObj: initialBudget,
};

Я определяю initialBudget отдельно, чтобы мы могли легко использовать его для сброса budgetObj.

Ваш редуктор обрабатывает действия, принимая state и action и возвращая следующее состояние, например:

const budgetReducer = (state, action) => {
  switch(action.type) {
    case 'SUBMIT_BUDGET':
      // I am using spread to clone the object to be safe, might not be 100% neccessary
      const budget = {...state.budget};
      // figure out where to add the current budget object
      const isIncome = budget.budgetType === '+';
      return {
        ...state, // not actually necessary in this case since we are updating every property
        incomes: isIncome ? state.incomes.concat(budget) : state.incomes, // maybe add to incomes
        expenses: isIncome ? state.expenses : state.expenses.concat(budget), // maybe add to expenses
        budgetObj: initialBudget, // reset budget object
      }
      default:
        return state;
  }
}
person Linda Paiste    schedule 05.02.2021
comment
Спасибо за подробный ответ, теперь он мне понятнее! Хотя я все еще не понимаю, как мне использовать свой собственный крючок useSemiPersistentState для доходов и расходов? Так как я хочу хранить свои доходы и расходы в локальном хранилище. - person alti21; 06.02.2021
comment
Создайте копию с именем useSemiPersistantReducer и замените useState на useReducer. Вместо [значение, setValue] используйте [значение, отправка]. Эффект все равно будет зависеть от [значение]. - person Linda Paiste; 06.02.2021