External Publication
Visit Post

React.js ~useState Antipatterns~

DEV Community [Unofficial] June 21, 2026
Source

1. Consider grouping related conditions together

  • Before

    const [x, setX] = useState(0); const [y, setY] = useState(0);

    const handlePointerMove = (e) => { setX(e.clientX); setY(e.clientY); }; return ( <div onPointerMove={handlePointerMove} style={{ width: "100vw", height: "100vh", }} /> );

  • After

    const [position, setPosition] = useState({ x: 0, y: 0 });

    const handlePointerMove = (e) => { setPosition({ x: e.clientX, y: e.clientY, }); }; return (... );

  • Before

    const [userName, setUserName] = useState(""); const [userAge, setUserAge] = useState(0);

  • After

    const [userInfo, setUserInfo] = useState({name:"",age:0});

2. Avoid declaring conflicting states

  • Before

    export default function Form() { const [text, setText] = useState(''); const [isSubmitting, setSubmitting] = useState(false); const [isSubmit, setIsSubmit] = useState(false);

    async function handleSubmit(e) { e.preventDefault(); isSubmitting(true); await sendMessage(text); isSubmitting(false); setIsSubmit(true); }

    if (isSubmit) { return

    I appreciate

    }

    function sendMessage(text) { return new Promise(resolve => { setTimeout(resolve, 2000); }); }

    return (

    <textarea disabled={isSending} value={text} onChange={e => setText(e.target.value)} />
    Submit {isSubmitting &&

    Submitting...

    } ); }

  • After

    export default function Form() { const [text, setText] = useState(''); const [status, setStatus] = useState('TYPING');

    async function handleSubmit(e) { e.preventDefault(); setStatus('SUBMITTING'); await sendMessage(text); setStatus('SUBMITTED'); }

    const isSending = status === 'SUBMITTING'; const isSent = status === 'SUBMITTED';

      return (... ); }

isSubmit and isSubmitting are conflicting and make it difficult to handle these states as it gets complex. You have to group them into a single state.

3. Avoid redundant use

export default function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [fullName, setFullName] = useState('');

  function handleFirstNameChange(e) {
    setFirstName(e.target.value);
    setFullName(e.target.value + ' ' + lastName);
  }

  function handleLastNameChange(e) {
    setLastName(e.target.value);
    setFullName(firstName + ' ' + e.target.value);
  }

  return (
    <>
      <label>
        First name:
        <input
      name="firstName"
          value={firstName}
          onChange={handleFirstNameChange}
        />
      </label>
      <label>
        Last name:
        <input
      name="lastName"
          value={lastName}
          onChange={handleLastNameChange}
        />
      </label>
      <p>
        FullName: <span>{fullName}</span>
      </p>
    </>
  );
}

The fullName can be calculated when rendering by firstName + lastName.

  const [userName, setUserName] = useState({ firstName: "", lastName: "" });
  const fullName = userName.firstName + " " + userName.lastName;

  const handleUserNameChange = (e) => {
    setUserName({ ...userName, [e.target.name]: e.target.value });
  };

And props should not be held in the state.

function Text({ children, color }) {
  const [textColor] = useState(color);

  return <h1 style={{ color: textColor }}>{children}</h1>;
}

export default function Example() {
  const [color, setColor] = useState("red");

  return (
    <div>
      <p>
        Select Color
        <select value={color} onChange={(e) => setColor(e.target.value)}>
          <option value="red">Red</option>
          <option value="blue">Blue</option>
          <option value="green">Green</option>
        </select>
      </p>
      <Text color={color}>Color will be changed</Text>
    </div>
  );
}

The color seems to change at a glance, but doesn't change. Because useState is initialized when first rendering.

  • Fixed

    function Text({ children, color }) { const textColor = color;

    return <h1 style={{ color: textColor }}>{children}; }

4. Avoid declaring the same state multiple times

const initialItems = [
  { id: 1, title: "taskA" },
  { id: 2, title: "taskB" },
  { id: 3, title: "taskC" },
];

export default function TaskList() {
  const [tasks, setTasks] = useState(initialItems);
  const [selectedTask, setSelectedTask] = useState(tasks[0]);

  function handleTaskChange(id, e) {
    setTasks(tasks.map((task) => (task.id === id ? { ...task, title: e.target.value } : task)));
    setSelectedTask((task) => (task.id === id ? { ...task, title: e.target.value } : task));
  }

  return (
    <>
      <h2>Task List</h2>
      <ul>
        {tasks.map((task, index) => (
          <li key={task.id}>
            <input
              value={task.title}
              onChange={(e) => {
                handleTaskChange(task.id, e);
              }}
            />
            <button
              onClick={() => {
                setSelectedTask(task);
              }}
            >
              Select
            </button>
          </li>
        ))}
      </ul>
      <p>Today's task {selectedTask.title}</p>
    </>
  );
}

The task and the selectedTask handle the same data. When you edit the task, you have to update selectedTask.

So, fix the codebase to calculate the selectedTask by id

function TaskList() {
  const [tasks, setTasks] = useState(initialItems);
  const [selectedTaskId, setSelectedTaskId] = useState(0);

  function handleTaskChange(id, e) {
    setTasks(tasks.map((task) => (task.id === id ? { ...task, title: e.target.value } : task)));
  }

  const selectedTask = tasks.find((task) => task.id === selectedTaskId);

  return (
    <>
      <h2>Task List</h2>
      <ul>
        {tasks.map((task) => (
          <li key={task.id}>
            <input
              value={task.title}
              onChange={(e) => {
                handleTaskChange(task.id, e);
              }}
            />
            <button
              onClick={() => {
                setSelectedTaskId(task.id);
              }}
            >
              Select
            </button>
          </li>
        ))}
      </ul>
      <p>Today's Task {selectedTask.title}</p>
    </>
  );
}

Discussion in the ATmosphere

Loading comments...