Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Lifting state up

State lives in one component, but it often has to be changed from another. The "Delete" button is in TodoItem, while the task array is in App. The solution is called lifting state up: keep the state in the closest common parent and pass children the data and the functions that change it through props.

A function in a prop

A parent passes a handler to a child like any other value. Change src/components/TodoItem.jsx:

// src/components/TodoItem.jsx
export default function TodoItem({ todo, onDelete }) {
  return (
    <li>
      {todo.text}
      <button onClick={() => onDelete(todo.id)}>Delete</button>
    </li>
  );
}

TodoItem does not know how deleting works. It only reports: "delete was clicked for the task with this id". Handler props are usually named with the on prefix.

Passing it through the list

TodoList gets the function from App and hands it to every item — src/components/TodoList.jsx:

// src/components/TodoList.jsx
import TodoItem from './TodoItem.jsx';
 
export default function TodoList({ todos, onDelete }) {
  return (
    <ul>
      {todos.map((todo) => (
        <TodoItem key={todo.id} todo={todo} onDelete={onDelete} />
      ))}
    </ul>
  );
}

State in the parent

src/App.jsx holds both the array and the function that changes it:

// src/App.jsx
import { useState } from 'react';
import TodoList from './components/TodoList.jsx';
 
export default function App() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Buy milk', done: false },
    { id: 2, text: 'Call Oleg', done: true },
  ]);
 
  function handleDelete(id) {
    setTodos(todos.filter((todo) => todo.id !== id));
  }
 
  return <TodoList todos={todos} onDelete={handleDelete} />;
}

Click "Delete" next to any task — it disappears from the list. Data flows down through props, events flow up through function calls.

Marking as done

A checkbox is wired the same way. In TodoItem:

// src/components/TodoItem.jsx
export default function TodoItem({ todo, onToggle, onDelete }) {
  return (
    <li>
      <input type="checkbox" checked={todo.done} onChange={() => onToggle(todo.id)} />
      {todo.text}
      <button onClick={() => onDelete(todo.id)}>Delete</button>
    </li>
  );
}

In App, add handleToggle from the lesson on arrays in state and pass onToggle={handleToggle} through TodoList. Clicking the checkbox marks the task.

Where to keep state

SituationWhere the state goes
Only one component needs itIn that component
Two siblings need itIn their common parent
Almost the whole app needs itAt the root or in context

Common mistake

A copy of a prop in the child's own state does not update when the parent changes:

// Wrong: done remembers the first value and drifts from App.
export default function TodoItem({ todo }) {
  const [done, setDone] = useState(todo.done);
}

If the data belongs to the parent, read it from props and change it through the function you were given.

Thanks for reading!

Donate to the site — we work hard for you.