Search code examples
javascriptreactjsreact-hooksuse-statereact-functional-component

SetState of an Objects of array in React


So i've been working on this for awhile what i'm trying to do is change a checked value on checkbox click.

My initial state looks like this:

const [todoList, setTodoList] = useState({
    foundation: {
      steps: [
        { key: "1", title: "setup virtual office", isDone: false },
        { key: "2", title: "set mission and vision", isDone: false },
        { key: "3", title: "select business name", isDone: false },
        { key: "4", title: "buy domain", isDone: false },
      ],
    },

    discovery: {
      steps: [
        { key: "1", title: "create roadmap", isDone: false },
        { key: "2", title: "competitor analysis", isDone: false },
      ],
    }
  });

and my map and onClick function (updateCheckFoundation works when click the checkbox)

    {todoList.foundation.steps.map((item) => {
        return (
          <div>
            <input type="checkbox" defaultChecked={item.isDone}
             onClick={(event)=> updateCheckFoundation({
                 isDone:event.target.checked, 
                 key:item.key
                 })}/>
            <span>{item.title}</span>
          </div>
        );
      })}

so how can ı update todoList use setState?

my code (updateCheckFoundation func.) like this and is not working :( :

const updateCheckFoundation = ({isDone, key}) => {
    const updateTodoList = todoList.foundation.steps.map((todo)=> {
     if(todo.key === key){
      return {
        ...todo,
        isDone
      };
    }
    return todo;

    });
    setTodoList(updateTodoList);
 

  }

Solution

  • Issue

    Your updateCheckFoundation callback isn't maintaining the state invariant, and is in fact, dropping all but the foundation.steps array of state.

    const updateCheckFoundation = ({isDone, key}) => {
      const updateTodoList = todoList.foundation.steps.map((todo)=> {
        if(todo.key === key){
          return {
            ...todo,
            isDone
          };
        }
        return todo;
    
      });
      setTodoList(updateTodoList); // <-- only the state.foundation.steps array!!
    }
    

    Solution

    In function components, when using the useState state updater functions you need to handle merging state (the root state), and nested state, yourself, manually.

    const updateCheckFoundation = ({ isDone, key }) => {
      setTodoList(state => ({
        ...state, // <-- shallow copy state object
        foundation: {
          ...state.foundation, // <-- shallow copy
          steps: state.foundation.steps.map(todo => todo.key === key
            ? { ...todo, isDone }
            : todo)
        },
      }));
    }