Skip to main content

How do you type state with useState()?

Basic cases

javascript
// Type inferred from the literal const [count, setCount] = useState(0); // number const [title, setTitle] = useState("Hello"); // string const [open, setOpen] = useState(false); // boolean

When you need to specify the type explicitly:

  • the initial value is null / undefined;
  • complex unions, objects, arrays;
  • you want to narrow the type (for example, literal types).
javascript
// Explicit typing with null const [user, setUser] = useState<User | null>(null); // Union type Status = "idle" | "loading" | "success" | "error"; const [status, setStatus] = useState<Status>("idle"); // Literal variants const [variant, setVariant] = useState<"primary" | "ghost">("primary");

Objects and partial updates

javascript
type Form = { name: string; age: number }; const [form, setForm] = useState<Form>({ name: "", age: 0 }); // Update based on the previous state setForm(prev => ({ ...prev, name: "Tim" }));

Tips • The type of prev is inferred automatically. • For multi-step forms and complex logic, consider useReducer.


Arrays and collections

javascript
const [items, setItems] = useState<string[]>([]); // Adding an item setItems(prev => [...prev, "new"]); // Set/Map (mutable!) - create new instances const [ids, setIds] = useState<Set<string>>(new Set()); setIds(prev => new Set(prev).add("42"));

Nullable / Optional done right

javascript
// Better: an explicit union with null const [el, setEl] = useState<HTMLDivElement | null>(null); // Same with undefined const [token, setToken] = useState<string | undefined>(undefined);

Don't use "whatever works" via as any: it breaks TS's guarantees.


Lazy initializer (for expensive computations)

javascript
const [big, setBig] = useState<number>(() => heavyCompute()); // The function runs once, on mount.

Derived types and "narrow" literals

javascript
// From API types type User = ApiUser & { fullName: string }; // A narrow literal via a const assertion const [align, setAlign] = useState<"left" | "center" | "right">("left"); // Or derive it from a settings object: const ALIGN = ["left", "center", "right"] as const; type Align = (typeof ALIGN)[number]; const [a, setA] = useState<Align>("left");

Request state (discriminated unions)

javascript
type LoadState = | { status: "idle" } | { status: "loading" } | { status: "success"; data: Post[] } | { status: "error"; error: string }; const [state, setState] = useState<LoadState>({ status: "idle" }); setState({ status: "loading" }); // ... setState({ status: "success", data });

Convenient for exhaustive switch statements without a "default".


Setter types and updater functions

javascript
const [n, setN] = useState(0); // Type: React.Dispatch<React.SetStateAction<number>> // You can pass a function that receives prev setN(prev => prev + 1);

Controlled inputs

javascript
const [value, setValue] = useState(""); const onChange = (e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value);

When to specify the generic explicitly

Specify <T> if:

  • the initial value is null/undefined;
  • arrays/objects/unions;
  • you want to "lock in" an exact type (for example, string vs string | undefined).

Quick examples:

javascript
// good const [data, setData] = useState<Post[] | null>(null); // good const [map, setMap] = useState<Map<string, number>>(new Map()); // good const [mode, setMode] = useState<"view" | "edit">("view"); // bad - too broad, any const [unknown, setUnknown] = useState<any>(null);

Common mistakes and how to avoid them

  • Initializing useState(null) without a type -> becomes null, and TS keeps complaining afterward. Fix: useState<User | null>(null)
  • Mutating complex structures (Set/Map, objects) -> React won't see the change. Fix: create a new structure.
  • State tied to many branches -> a tangled union. Fix: think about useReducer.

Summary

  1. Trust type inference, but specify <T> for null/undefined and complex shapes.
  2. For objects and arrays: immutable updates.
  3. For loading scenarios: discriminated unions.
  4. Don't be afraid of the lazy initializer and literal types: they make state precise and reliable.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.