I'm trying to manipulate the api response in order to render cleaner way as posible.
I have this state:
state = {
tasks: {
workingProgress: [],
toDo: [],
done: [],
},
};
And from api I get objects like these:
{id: "1", task: "add button", team: "Metro", status: "workingProgress", …}
{id: "8", task: "fixed accessibility SR", team: "Metro", status: "toDo", …}
My idea is transform my state in something like this:
tasks: {
workingProgress: [{id: "1", task: "add button", team: "Metro", ...},{}, {}],
toDo: [{id: "8", task: "fixed accessibility SR", team: "Metro", ...}, {}, {}],
done: [],
},
I have this and works as a charm:
state = {
tasks: {
workingProgress: [],
toDo: [],
done: [],
},
};
componentDidMount() {
TasksAPI.getAll().then((res) => {
this.setState(({ tasks }) => {
res.forEach((item) => {
if (tasks[item.status]) {
tasks[item.status].push(item);
}
});
return { tasks };
});
});
}
My question is because I intend to change from "setState" code from hooks.
const [tasks, setTasks] = useState({
workingProgress: [],
toDo: [],
done: [],
});
const history = useHistory();
useEffect(() => {
TasksAPI.getAll().then((res) => {
setTasks(({ tasks }) => {
res.forEach((item) => {
if (tasks[item.status]) {
tasks[item.status].push(item);
}
});
return { tasks };
});
});
}, []);
But when I try this my code is break:
Tasks.js:21 Uncaught (in promise) TypeError: Cannot read property 'toDo' of undefined
This is my return:
<div>
{Object.entries(tasks).map(([key, status]) => (
<div key={key}>
<div >
<ol>
{status.map((item) => (
<li>
<div>
......
</div>
Is my "hooks" approach incorrect?
CodePudding user response:
I think the problem lies inside the code of your useEffect and specifically setTasks(({ tasks }) =>... and return { tasks };, which have 2 main problems:
- In this piece of code you create a local
tasksvariable that overiddes yourtasksfrom state, returned byuseState. - You also trying to destruct this state and grab
tasksproperty but this is wrong since your state is already the tasks. You carried this logic from the previous implementation where yourstatewas an object containing atasksproperty.
Your useEffect should be changed like below:
useEffect(() => {
TasksAPI.getAll().then((res) => {
setTasks((previousTasks) => {
const newTasks = {...previousTasks}
res.forEach((item) => {
if (newTasks[item.status]) {
newTasks[item.status] = [...newTasks[item.status], item]
}
});
return newTasks;
});
});
}, []);
newTasks = [...previousTasks] and newTasks[item.status] = [...newTasks[item.status], item] ensure that you don't mutate the previous tasks object and status arrays and create new ones.
Even your 1st solution (with classes) does not tackle this problem which can lead to bugs with react updates.
However if you don't care about deep immutability you can simplify the code inside forEach to (although i wouldn't advise so):
if (newTasks[item.status]) {
newTasks[item.status].push(item);
}
CodePudding user response:
Issue
From what I can see you've a simple state mutation issue. The effect hook callback is for-each'ing over the res array and directly pushing into the tasks state instead of creating new array and state references.
You are also attempting to destructure tasks from the tasks state, which of obviously not a defined property. When it was this.state.tasks and "previousState" was this.state you could destructure tasks, but now in the useState hook tasks is the state! You then return an object return { tasks }; with a single tasks property with the real state nested deeper, i.e. making it tasks.tasks.toDo for example.
Solution
Instead of looping through and enqueueing multiple state updates, which necessarily need to correctly update from each previously enqueued update, just loop though and create the new next state object and enqueue a single state update.
const [tasks, setTasks] = useState({
workingProgress: [],
toDo: [],
done: [],
});
useEffect(() => {
TasksAPI.getAll()
.then((res) => {
setTasks(tasks => res.reduce(
(tasks, task) => ({
...tasks, // <-- shallow copy the state
[task.status]: [
...tasks[task.status], // <-- shallow copy array
task], // <-- append new task item
}),
tasks, // <-- start from the previous state
));
});
}, []);
