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
tasks
variable that overiddes yourtasks
from state, returned byuseState
. - You also trying to destruct this state and grab
tasks
property but this is wrong since your state is already the tasks. You carried this logic from the previous implementation where yourstate
was an object containing atasks
property.
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
));
});
}, []);