#reactjs #redux #react-redux
#reactjs #сокращение #реагировать-redux #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 не выполняет повторный запуск. Добавленная тренировка отображается только после обновления страницы. Я предполагаю, что это происходит потому, что мне нужно обновить состояние вложенного массива workout, чтобы заставить 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:
Ваш редуктор не обновляет правильные вещи. Вы фактически преобразуете свойство workouts
, которое было массивом, в объект, который имеет свойство workouts
, которое является массивом. Это совсем не то, что вы хотите сделать.
Поиск и обновление элемента массива в redux — это не проблема, когда легко иметь plans
объект, который вводится идентификатором плана. Имеет ли значение порядок планов? Если это произойдет, я бы все равно сохранил планы с ключом 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;
}
}
С помощью предложенных редукторов вам не нужно вносить какие-либо изменения в свое действие, но вам нужно изменить свой селектор, потому plans
что он больше не является массивом. Ваш селектор становится (state) => Object.values(state.plans)
.
Если вы сохранили конкретный заказ плана, вы должны выбрать заказ и сопоставить его с отдельными планами: (state) => state.planOrder.map( id => state.plans[id] )
.