I wrote eleven small components that each trigger one of these errors, compiled them with typescript 7.0.2 and @types/react 19.3.0 under strict, then ran the same file through typescript 5.9.3. The diagnostics were identical on both compilers, so nothing here depends on which tsc your project is on. Every message below is pasted from the compiler output, not paraphrased.
TL;DR: TS2349 means the thing you're calling isn't a function, and in React that's a swapped
useStatetuple nine times out of ten. TS2531 means the compiler proved a null is possible and you assigned through it; reads produce its sibling TS18047, and calls produce TS2721 or TS2722. Fix the type, not the symptom: reorder the tuple, narrow the union, or use?.on the ref.
Which Code Did You Actually Get?
The two codes in the title are the ones people search for, but the compiler hands out five different numbers for what feels like two mistakes. This table is the output of my test file, one row per case:
| What the code did | Diagnostic |
|---|---|
const [setCount, count] = useState(0); setCount(1) | TS2349: This expression is not callable. Type 'Number' has no call signatures. |
const { count, setCount } = useState(0) | TS2339: Property 'count' does not exist on type '[number, Dispatch<SetStateAction |
const [count] = useState(0); count() | TS2349: Type 'Number' has no call signatures. |
useState<string | (() => void)>('x') then handler() | TS2349: Not all constituents of type 'string | (() => void)' are callable. |
useReducer state called as a function | TS2349: Type 'Number' has no call signatures. |
useRef<HTMLInputElement>(null) then inputRef.current.focus() | TS18047: 'inputRef.current' is possibly 'null'. |
useState<User | null>(null) then user.name in JSX | TS18047: 'user' is possibly 'null'. |
document.getElementById('root').textContent = 'hi' | TS2531: Object is possibly 'null'. |
document.querySelector('.x') then el.classList.add() | TS18047: 'el' is possibly 'null'. |
useState<(() => void) | null>(null) then onDone() | TS2721: Cannot invoke an object which is possibly 'null'. |
optional prop onClick?: () => void then onClick() | TS2722: Cannot invoke an object which is possibly 'undefined'. |
Worth pausing on the TS2531 row. It's the only one. Every other possibly-null read in the file came back as TS18047, which TypeScript introduced to put the variable name in the message. TS2531 survived in exactly one spot: an assignment through a possibly-null object. So if you're seeing TS2531 on a modern compiler, look for a = to the right of the property access. If you're seeing it everywhere, you're on an old TypeScript, and the fixes are the same.
Why Does TS2349 Follow a Swapped useState Tuple?
Because useState returns a tuple, and a tuple is positional. @types/react types it as [S, Dispatch<SetStateAction<S>>], so whatever name you put first is the state and whatever you put second is the setter. Names mean nothing to the compiler.
// What I wrote
const [setCount, count] = useState(0);
return <button onClick={() => setCount(1)}>{count}</button>;
error TS2349: This expression is not callable.
Type 'Number' has no call signatures.
error TS2322: Type 'Dispatch<SetStateAction<number>>' is not assignable to type 'ReactNode'.
The second error is the tell. If a TS2349 on your setter is paired with a TS2322 about Dispatch<SetStateAction<...>> not being a ReactNode, the tuple is backwards. You tried to render the setter. Swap the names and both errors leave together.
The object-destructuring version produces TS2339 instead, because you asked a tuple for a property it doesn't have. React's docs are consistent about array destructuring for a reason, and it isn't style: the tuple is the API.
The union case is the one I'd call a real design error rather than a typo:
const [handler, setHandler] = useState<string | (() => void)>('x');
return <button onClick={() => handler()}>go</button>;
error TS2349: This expression is not callable.
Not all constituents of type 'string | (() => void)' are callable.
Type 'string' has no call signatures.
The compiler is right, and the fix is to admit that state holding either a label or a function was a shortcut. Split it into two pieces of state, or make the union a discriminated union with a kind field you can switch on. typeof handler === 'function' narrows it too, if the shape is forced on you by a library.
Names mean nothing to a tuple: whatever you write first in a useState destructure is the state, whatever you write second is the setter, however you spelled them.
Why Is ref.current Possibly Null After Mount?
Because the type says so and the compiler can't see React's lifecycle. In @types/react 19.3, useRef<HTMLInputElement>(null) returns RefObject<HTMLInputElement | null>. React fills current during commit, before your effect runs, but that's a runtime fact. The type has no "after mount" state.
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => { inputRef.current.focus(); }, []);
error TS18047: 'inputRef.current' is possibly 'null'.
The honest fix is inputRef.current?.focus(). It's one character more than the bang, and it stays correct the day someone wraps the input in a condition. I reach for if (inputRef.current === null) { return; } when the effect does more than one thing with the element, because a chain of ?. across five lines reads worse than one guard at the top.
What about the bang, inputRef.current!.focus()? It compiles. I'd still not merge it, and here's the opinion part: every non-null assertion in a component is a bet that the null branch never happens, placed by the person who understands the component least well, which is whoever edits it next. The guard costs nothing. The bet costs a production TypeError eventually.
How Do You Render Nullable State Without a Null Check on Every Line?
Push the check up. The user.name error is the common one:
const [user, setUser] = useState<User | null>(null);
return <p>{user.name}</p>;
error TS18047: 'user' is possibly 'null'.
You could write user?.name and move on. For one field, do that. For a component that reads six fields off user, return early instead:
if (user === null) {
return <p>Loading</p>;
}
return (
<article>
<h2>{user.name}</h2>
<p>{user.id}</p>
</article>
);
After the early return, user is User for the rest of the function. That's control-flow narrowing, and it's the same mechanism that makes the kind switch work in a discriminated union. Does the loading branch belong in this component at all? Often not. If the data comes from a query library, the TanStack Query guide shows the pattern where the parent owns the null and the child receives a User that can't be null, and the error never appears.
When Is the Null a Callback?
Then the code is TS2721 or TS2722, and the fix is the optional call operator:
export function ButtonH({ onClick }: { onClick?: () => void }) {
return <button onClick={() => onClick()}>go</button>;
}
error TS2722: Cannot invoke an object which is possibly 'undefined'.
Write onClick?.(). It calls the function if there is one and evaluates to undefined otherwise. For an optional prop that's exactly the semantics you wanted. For state holding a callback, useState<(() => void) | null>(null), the same onDone?.() works, though storing a function in state is itself a smell worth a second look: a function in useState is called by React on initialization if you pass it directly, so you'd need useState(() => fn), and most of the time a useRef or a plain prop is what the code meant.
Why Do These Errors Vanish With strict Off, and Why That's Worse?
Every TS2531, TS18047, TS2721 and TS2722 in the table is a strictNullChecks diagnostic. Turn the flag off and the file compiles, and inputRef.current.focus() throws on the first render where the ref is empty. The error didn't go away. It moved from your terminal to your users.
I ran the eleven cases through the strict-mode ladder to confirm which ones depend on which flag, and the split is clean: TS2349 and TS2339 appear with no strict flags at all, because calling a number is wrong under any configuration. The null family needs strictNullChecks. If your project is mid-migration and you're seeing only the TS2349 kind, that's not a clean bill of health; it's the null checks you haven't turned on yet.
TypeScript 7 changes the default here. On the sixty-line legacy sample from the strict-mode guide, tsc 7.0.2 with no config and no flags reported eight errors; tsc 5.9.3 on the same file reported none. If your first encounter with TS18047 came the week you upgraded, that's why.
How Do You Read the Message Before Searching for It?
Three habits that cut my own debugging time more than any of the fixes above:
- Read the second line. TS2349 always names the type that isn't callable, and 'Number' versus 'string | (() => void)' sends you to different fixes.
- Look for a paired error on the same line or the next. A TS2349 next to a TS2322 about
Dispatchis a swapped tuple; a TS2349 on its own is a call on a value. - Check whether the possibly-null thing is read, assigned or called. Read gives TS18047, assignment gives TS2531, call gives TS2721 or TS2722, and each wants a different operator:
?., a guard, or?.().
None of this is clever. It's the difference between pasting a code into a search box and fixing the line in the time it takes the page to load.