How do you type `useState` in TypeScript? Do you always have to pass it the type?
Show answer — try answering out loud first
Not always. If the initial value is a primitive, TypeScript infers the type on its own. The explicit generic is needed when the initial value doesn't represent all the possible values, like useState<Usuario | null>(null) or useState<Tarea[]>([]).
useState is a generic hook: useState<T>(). TypeScript tries to deduce T from the initial value you pass it.
useState(0)infersnumber,useState("hola")infersstring,useState(false)infersboolean. In these cases don't write the generic: it's noise.- The problem shows up when the initial value "falls short". If you start with
nullbecause the user hasn't loaded yet,useState(null)infers that the state isnullforever, and you won't be able to store a user in it later. - Same with empty arrays:
useState([])infersnever[], an array where nothing fits. You need to tell it what it will contain:useState<Tarea[]>([]). The practical rule: use the generic when the state can hold more types than the initial value shows. - With
useRefit's similar: for DOM references useuseRef<HTMLInputElement>(null), and TypeScript will know thatref.currentisHTMLInputElement | null.
import { useState, useRef } from "react";
type Usuario = { id: number; nombre: string };
type Tarea = { id: number; titulo: string; completada: boolean };
function Panel() {
// Automatic inference: the generic isn't needed
const [contador, setContador] = useState(0); // number
const [busqueda, setBusqueda] = useState(""); // string
// Generic needed: starts as null, later it will be a Usuario
const [usuario, setUsuario] = useState<Usuario | null>(null);
// Generic needed: [] only infers never[]
const [tareas, setTareas] = useState<Tarea[]>([]);
// useRef for the DOM: current is HTMLInputElement | null
const inputRef = useRef<HTMLInputElement>(null);
function agregarTarea() {
setTareas([...tareas, { id: Date.now(), titulo: busqueda, completada: false }]);
setBusqueda("");
inputRef.current?.focus(); // optional chaining because it can be null
}
return (
<div>
<p>{usuario ? `Hola, ${usuario.nombre}` : "Sin sesión"}</p>
<input ref={inputRef} value={busqueda} onChange={(e) => setBusqueda(e.target.value)} />
<button onClick={agregarTarea}>Agregar ({tareas.length})</button>
<button onClick={() => setContador(contador + 1)}>Clics: {contador}</button>
</div>
);
}
Starting a state with null or [] without a generic and crashing when updating it:
const [usuario, setUsuario] = useState(null);
setUsuario({ id: 1, nombre: "Ana" });
// Error: Argument of type '{ id: number; nombre: string; }' is not assignable to parameter of type 'SetStateAction<null>'
const [tareas, setTareas] = useState([]);
setTareas([{ id: 1, titulo: "Estudiar", completada: false }]);
// Error: Type '{ id: number; ... }' is not assignable to type 'never'
The fix is to declare the full type of the state:
const [usuario, setUsuario] = useState<Usuario | null>(null);
const [tareas, setTareas] = useState<Tarea[]>([]);
"When the initial value is a primitive, I let TypeScript infer the type:
useState(0)already knows it'snumber. I write the generic when the initial value doesn't tell the whole story: if I start withnullbecause the data hasn't loaded yet, I useuseState<Usuario | null>(null), and if I start with an empty array I useuseState<Tarea[]>([]), because[]is only inferred asnever[]. WithuseReffor the DOM I do the same:useRef<HTMLInputElement>(null), and that waycurrentis properly typed."
Type the state of a component that stores the selected producto (there may be none) and the list of favoritos (numeric ids), and add a favorite.
See solution
import { useState } from "react";
type Producto = { id: number; nombre: string; precio: number };
function Tienda() {
const [producto, setProducto] = useState<Producto | null>(null);
const [favoritos, setFavoritos] = useState<number[]>([]);
function agregarFavorito(id: number) {
setFavoritos([...favoritos, id]);
}
return (
<div>
<p>{producto ? producto.nombre : "Elige un producto"}</p>
<button onClick={() => setProducto({ id: 1, nombre: "Teclado", precio: 500 })}>Seleccionar</button>
<button onClick={() => producto && agregarFavorito(producto.id)}>Favorito ({favoritos.length})</button>
</div>
);
}