useState is generic, and most of the time you should let it infer: useState('') gives you a string and a setter for it. Supply the type argument only when the initial value is too narrow for the states that follow — an empty array, or a null placeholder for something that arrives later.
import { useState } from 'react';
interface Todo { id: number; text: string; done: boolean }
export function useTodos() {
const [query, setQuery] = useState(''); // inferred: string
const [todos, setTodos] = useState<Todo[]>([]); // [] alone infers never[]
const [editing, setEditing] = useState<Todo | null>(null);
return { query, setQuery, todos, setTodos, editing, setEditing };
}
export function useBroken() {
const [todos, setTodos] = useState([]);
const [user, setUser] = useState(null);
setTodos([{ id: 1, text: 'Ship', done: false }]);
setUser({ name: 'Ada' });
}src/hooks.ts(13,13): error TS2322: Type '{ id: number; text: string; done: false; }' is not
assignable to type 'never'.
src/hooks.ts(14,13): error TS2353: Object literal may only specify known properties, and 'name'
does not exist in type '(prevState: null) => null'.Both messages are baffling the first time, and both come from widening. [] widens to never[], an array that can hold nothing. null widens to the type null, leaving the setter's updater-function overload as the only one that could still match — hence a complaint about (prevState: null) => null rather than about the object you passed.
The rest of the Hooks API infers cleanly: useMemo from its callback's return value, useCallback from the function you pass, useEffect from nothing. useEffect does enforce one rule — its callback must return void or a cleanup function, so useEffect(() => fetchData(), []) fails with Type 'Promise<number>' is not assignable to 'void | Destructor'. The fix is a braced body.
An array returned from a custom Hook infers as an array of the union of its elements, which is never useful. as const makes it a readonly tuple, so destructuring gives each name its own type.
function useToggle(initial = false) {
const [on, setOn] = useState(initial);
return [on, () => setOn(v => !v)] as const; // readonly [boolean, () => void]
}Without it, useToggle() returns (boolean | (() => void))[] and the error lands far from its cause, at the JSX writing onClick={toggleOpen}. Prefer an object return unless callers should rename both values freely.