A tuple hook with as const and a generic hook
localStorage in the preview is in memory and resets on each Run, so visits always starts at 0 here; in a real browser it would survive a reload. JSON.parse(saved) as T is a promise, not a check: if someone stored a string under visits, the type would lie (Topic 12.1). A production hook would validate it or catch parse errors.
Create context as createContext<Value | null>(null) and read it through a guard hook that throws outside the provider, so callers never see null; return tuples from custom hooks as const, and make hooks generic when the caller decides the type.
Change the code and press Run (Ctrl+Enter). Predict what will happen first, then break it on purpose and read what changes. Your edits are saved and match the lesson page.
Ready for more? This phase has 5 hands-on exercises with automatic checks.
Practice questions
Write the code in the editor, run it, then open the model answer to compare.
Create a typed CartContext with items: string[], add(item: string) and count: number, a CartProvider, and a guard hook useCart. Show the count in a header and an Add button in another component.
Write a generic hook useList<T>(initial: T[]) that returns { items, add, removeAt }, and use it with numbers and with objects.
Explain it without notes
Why use createContext<Value | null>(null) instead of createContext({} as Value)?
What does a guard hook do, and how does it change the type callers see?
Why does a custom hook returning [value, setter] need as const?
How does TypeScript decide T in a generic hook like useStoredState<T>(key, initial)?
Preview
Console
Nothing logged yet.Expected console output
open: false visits: 0 tags: []
open: true visits: 0 tags: []
open: true visits: 1 tags: []
open: true visits: 1 tags: [ 't0' ]
stored: 1 ["t0"]