Mesrai
Back to blog
// essayTechnical Deep Dive

Non-Null Assertion on Auth Context: The Race-To-Mount Bug

Real PR catch: `user!.name` on async-loaded state — null on first render, runtime TypeError. Skeleton guard fix.

Mesrai TeamAugust 12, 20268 min read

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

tsxcomponents/Header.tsx
// 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:

textexploit
# 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

textreview-comment.txt
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

tsxcomponents/Header.tsx (fixed)
// 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.

// try

See it on your next PR.

Free for individuals. Install in two minutes. Mesrai reviews every commit.