SyntaxStudy
Sign Up
React State with Objects and Arrays
React Beginner 1 min read

State with Objects and Arrays

When state holds an object or an array, React requires you to replace it with a new value rather than mutating the existing one. React uses shallow equality (`Object.is`) to decide whether state has changed, so mutating an object in place and passing the same reference to the setter will not trigger a re-render. Always create a fresh copy using the spread operator, `Object.assign`, `Array.prototype.map`, `filter`, `concat`, or similar techniques. Updating a nested property requires spreading at every level of nesting. For deeply nested state this becomes verbose; `useReducer` or the Immer library (which lets you write "mutating" code that is actually wrapped in an immutable update) are common solutions. For arrays, prefer `map` over index assignment, `filter` over `splice`, and the spread operator over `push` or `pop`. These functional approaches never mutate the original and always return new references that React can detect. Keeping state flat is the best long-term strategy. If you find yourself spreading several layers deep, it is a signal to restructure state or lift some data into a separate piece of state. Normalising arrays of objects (storing them by ID in an object rather than a plain array) can also simplify updates significantly.
Example
import { useState } from 'react';

// ── Object state ──────────────────────────────────────────────
function ProfileForm() {
  const [profile, setProfile] = useState({ name: '', email: '', bio: '' });

  // CORRECT: spread to create a new object
  const updateField = (field, value) =>
    setProfile(prev => ({ ...prev, [field]: value }));

  return (
    <form>
      <input value={profile.name}  onChange={e => updateField('name',  e.target.value)} placeholder="Name" />
      <input value={profile.email} onChange={e => updateField('email', e.target.value)} placeholder="Email" />
      <textarea value={profile.bio} onChange={e => updateField('bio', e.target.value)} placeholder="Bio" />
      <pre>{JSON.stringify(profile, null, 2)}</pre>
    </form>
  );
}

// ── Array state ───────────────────────────────────────────────
function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Learn React', done: false },
    { id: 2, text: 'Build a project', done: false },
  ]);

  // Add
  const addTodo = (text) =>
    setTodos(prev => [...prev, { id: Date.now(), text, done: false }]);

  // Toggle
  const toggle = (id) =>
    setTodos(prev => prev.map(t => t.id === id ? { ...t, done: !t.done } : t));

  // Remove
  const remove = (id) =>
    setTodos(prev => prev.filter(t => t.id !== id));

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          <span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
                onClick={() => toggle(todo.id)}>
            {todo.text}
          </span>
          <button onClick={() => remove(todo.id)}>✕</button>
        </li>
      ))}
    </ul>
  );
}

export default TodoList;