A header component asserting `user!` non-null. State is loaded asynchronously by an effect. First render: user is null. The assertion silences TypeScript but the runtime still reads from null.
The vulnerable diff
// components/Header.tsx
export function Header() {
const [user, setUser] = useState<User | null>(null);
useEffect(() => { getCurrentUser().then(setUser); }, []);
return (
<header>
{/* BUG: user is null on first render. ! lies. */}
<span>Hi, {user!.name}</span>
</header>
);
}What is wrong
The non-null assertion operator (`!`) tells TypeScript 'trust me, this is not null'. It does not change the runtime. For values that are genuinely null at certain moments — async-loaded state, optional config, etc — `!` is a lie that the runtime exposes via `Cannot read property 'name' of null`. The right approach is to handle the null case explicitly: a skeleton, a loading state, or a Suspense boundary.
The attack
Runtime:
# Browser console on first paint:
TypeError: Cannot read properties of null (reading 'name')
at Header (Header.tsx:9:32)User sees error boundary, app feels broken even though it works after the effect fires.
Mesrai's review comment
mesraipilot · Bot · reviewed 1 min ago
[mesrai] [code-review] [Language] [NonNull] [medium]
`user!.name` asserts non-null but user IS null on first render —
useEffect hasn't fired yet. Type system silenced, runtime crashes.
Guard explicitly:
if (!user) return <Skeleton />;
return <header><span>Hi, {user.name}</span></header>;
Or use Suspense (React 18+):
<Suspense fallback={<Skeleton />}>
<UserGreeting /> // reads user via a Suspense-aware hook
</Suspense>
Pattern rule: never use `!` on async-loaded state. Use it only when
TypeScript can't see a guarantee you can prove — e.g. after an
explicit if-check it didn't narrow.The fix
// components/Header.tsx — fixed
export function Header() {
const [user, setUser] = useState<User | null>(null);
useEffect(() => { getCurrentUser().then(setUser); }, []);
if (!user) return <header><Skeleton /></header>;
return <header><span>Hi, {user.name}</span></header>;
}Null check, render skeleton, then the typed-narrow path. TypeScript narrows `user` to non-null after the guard.
Why human review missed it
Non-null assertion is a footgun the type system makes easy. The bug is at the boundary between type system and runtime. Mesrai catches `!` operators on async-loaded state values, where the null possibility is genuine.
Related rules + further reading
Mesrai rule pack: language/no-nonnull-async-state — flags `!` on values produced by async effects or queries.
TS-ESLint: no-non-null-assertion (broader rule).
Common React bug class.
Takeaway
Non-null assertion lies when state is async. Guard explicitly. Mesrai catches every `!` on stateful values.