Hydration Proof

Search documentation

Find a page or section

HP5002: User input was reset during hydration

HP5002 (input-reset) means text typed or a box checked before hydration finished was cleared or replaced. Keep what users type with uncontrolled inputs.

HP5002 (input-reset) means text a user typed, or a checkbox they checked, before the page finished hydrating was cleared or replaced when React took over. The user loses their input without a warning. Use uncontrolled inputs or read the current DOM value on mount, and fix any hydration mismatch around the form.

CodeHP5002
Nameinput-reset
Default severityError
GroupInteraction during hydration
What it meansText typed or an option chosen before hydration finished was cleared or replaced.

What the HP5002 input reset finding means

The finding comes from the interaction checks (checks.interactions: true or --interactions). hydration-proof loads the page with its scripts held back, like a user on a slow connection, types hydration-proof check into the first text field and checks the first checkbox. Then it lets the page hydrate and reads the controls again:

Text typed into this field before the page finished hydrating was cleared.
A checkbox checked before the page finished hydrating was unchecked by hydration.

When React replaced the field itself, the finding adds: "React replaced the field during hydration, so the new field lost what was typed." It is an error, because the user's work is gone.

Likely causes

  • A hydration mismatch in the form or around it. React throws away that branch and renders it again, with new, empty inputs; the finding usually sits next to HP1010 or HP1011.
  • An effect that sets the value on mount, such as restoring a saved draft or a value from the URL.
  • Controlled inputs whose state is reset by a re-render during hydration.
  • Server Action form state that the client hydrates without.

How to fix it

  1. Fix any hydration mismatch around the form. A re-rendered branch creates new, empty inputs, so this comes first.
  2. Use uncontrolled inputs (defaultValue, defaultChecked). The browser keeps what was typed, and React hydrates the field as it is.
  3. Or read the current DOM value when the component mounts, so text typed before hydration is kept in state.

An effect that overwrites the field:

components/search.tsx
"use client";
 
import { useEffect, useState } from "react";
 
// Before: restoring the last query replaces what the user typed
export function Search() {
  const [query, setQuery] = useState("");
  useEffect(() => {
    setQuery(sessionStorage.getItem("last-query") ?? "");
  }, []);
  return (
    <input value={query} onChange={(event) => setQuery(event.target.value)} />
  );
}
components/search.tsx
"use client";
 
import { useEffect, useRef, useState } from "react";
 
// After: what the user already typed wins over the saved value
export function Search() {
  const inputRef = useRef<HTMLInputElement>(null);
  const [query, setQuery] = useState("");
  useEffect(() => {
    const typed = inputRef.current?.value ?? "";
    setQuery(
      typed !== "" ? typed : (sessionStorage.getItem("last-query") ?? "")
    );
  }, []);
  return (
    <input
      ref={inputRef}
      value={query}
      onChange={(event) => setQuery(event.target.value)}
    />
  );
}

An uncontrolled field needs none of this:

components/newsletter.tsx
export function Newsletter() {
  return (
    <form action="/subscribe" method="post">
      <input name="email" type="email" defaultValue="" />
      <label>
        <input name="weekly" type="checkbox" defaultChecked={false} /> Weekly
      </label>
      <button type="submit">Subscribe</button>
    </form>
  );
}

When it is acceptable

A field that should reset on load (a one-time code, for example) can be ignored with an ignore.issues rule for code: "HP5002", its route and a reason; see ignoring findings.