Home > Enterprise >  From previous state version to hooks
From previous state version to hooks

Time:11-18

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:

  1. In this piece of code you create a local tasks variable that overiddes your tasks from state, returned by useState.
  2. 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 your state was an object containing a tasks 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
      ));
  });
}, []);
  • Related