我正在尝试学习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;
}
我看过很多关于如何更新嵌套数组状态的教程,并尝试了一些不同的方法,但是我似乎在这里找不到正确的解决方案。
有人知道如何解决此问题吗?
答案 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] )
。