React Redux更新嵌套数组的状态

时间:2020-10-15 21:29:23

标签: reactjs redux react-redux

我正在尝试学习React(使用Redux),因此我正在制作一个应用程序,可以在其中创建锻炼计划,向其中添加锻炼,然后向锻炼添加锻炼。

PlanListComponent

import { Button, Card, Typography } from "@material-ui/core/";
import { makeStyles } from "@material-ui/core/styles";
import DeleteIcon from "@material-ui/icons/Delete";
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { deletePlan, getPlans } from "../actions/plansActions";
import AddWorkouts from "./AddWorkouts";

const useStyles = makeStyles((theme) => ({}));

function PlansList() {
  const classes = useStyles();

  const { plans } = useSelector((state) => state.plans);
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(getPlans());
  }, [dispatch]);

  return (
    <div>
      {plans.map((plan) => (
        <Card key={plan._id}>
          <Typography>{plan.name}</Typography>
          <Typography>{plan._id}</Typography>
          <div>
            {plan.workouts.map((workout) => (
              <li key={workout._id}>{workout.description}</li>
            ))}
          </div>
          <AddWorkouts plan={plan} />
          <Button onClick={() => dispatch(deletePlan(plan._id))}>
            <DeleteIcon /> Delete
          </Button>
        </Card>
      ))}
    </div>
  );
}

export default PlansList;

我的PlansList组件为每个计划呈现一张卡片。在此卡片中,它为该计划内的每个锻炼呈现锻炼列表。将锻炼添加到计划后,PlansList组件不会重新呈现。添加的锻炼仅在刷新页面后显示。我猜测是因为我必须更新嵌套锻炼数组的状态才能使React重新渲染我的组件。

这些是我在计划中添加锻炼的动作和简化方法。我在执行操作时发送的有效负载是一个对象数组。

操作

export const addWorkouts = (workouts, planId) => (dispatch, getState) => {
  axios
    .post(`/workouts/${planId}`, workouts, tokenConfig(getState))
    .then(res =>
      dispatch({
        type: ADD_WORKOUTS,
        id: planId
        payload: res.data
      }));
}

减速器

const initialState = {
  plans: [{
    workouts: []
  }],
  isLoading: false,
};

export default function (state = initialState, action) {
  switch (action.type) {
    case ADD_WORKOUTS:
      return {
        ...state,
        plans: {
          // guessing i should find the right plan by id here
          ...state.plans,
          workouts: {
            ...state.plans.workouts,
            workouts: state.plans.workouts.concat(action.payload)
          }
        }
      };
    default:
      return state;
  }

我看过很多关于如何更新嵌套数组状态的教程,并尝试了一些不同的方法,但是我似乎在这里找不到正确的解决方案。

有人知道如何解决此问题吗?

1 个答案:

答案 0 :(得分:1)

您的减速器没有更新正确的东西。您实际上是在将属性workouts(它是一个数组)转换为一个对象,该对象具有一个属性workouts(它是一个数组)。这根本不是你想要做的。

在容易使plans成为由计划ID用作键的对象的情况下,在redux中查找和更新数组元素将不起作用。计划的顺序重要吗?如果是这样,我仍将计划按id进行键控,但是在状态下,我将有一个单独的值,用于存储计划id的有序数组。

我的建议如下:

const initialState = {
  plans: {}, // you could call this plansById
  isLoading: false,
  planOrder: [] // totally optional and only needed if the order matters when selecting all plans
};

export default function (state = initialState, action) {
  switch (action.type) {
    case ADD_WORKOUTS:
      const existingPlan = state.plans[action.id] || {};
      return {
        ...state,
        plans: {
          ...state.plans,
          [action.id]: {
            ...existingPlan,
            workouts: ( existingPlan.workouts || [] ).concat(action.payload)
          }
        }
      };
    default:
      return state;
  }

您可以将异径管切成小块。如果您有多个更新计划的动作,则可以制作一个简化器来处理单个计划,这样就不必重复所有...东西了。

function planReducer (planState = { workouts: []}, action ) {
  switch (action.type) {
    case ADD_WORKOUTS:
      return {
        ...planState,
        workouts: planState.workouts.concat(action.payload)
      };
      default:
        return planState;
    }
}

function rootReducer (state = initialState, action) {
  /**
   * you can use a `switch` statement and list all actions which modify an individual plan
   * or use an `if` statement and check for something, like if the action has a `planId` property 
   */
  switch (action.type) {
    case ADD_WORKOUTS:
    case OTHER_ACTION:
      return {
        ...state,
        plans: {
          ...state.plans,
          [action.id]: planReducer(state.plans[action.id], action),
        }
      };
    default:
      return state;
  }
}

使用建议的reducer,您不需要对操作进行任何更改,但是您确实需要更改选择器,因为plans不再是数组。您的选择器将变为(state) => Object.values(state.plans)

如果存储了特定的计划订单,则应选择该订单并将其映射到各个计划:(state) => state.planOrder.map( id => state.plans[id] )