Compute once, reuse
Lazy-init state, cache pure results, and build each RegExp once.
Vercelvercel.com/blog
Reference: How we made the Vercel Dashboard twice as fast
From vercel-react-best-practices: rerender-lazy-state-init and 2 more.
Lazy state initialization
Type in both boxes, then raise the item count.
Eager argumentBad
- buildSearchIndex calls
- 1
- Last render
- 0 ms
0 ms16 ms frame32 ms
useState(buildSearchIndex(items))discarded 0Lazy initializerGood
- buildSearchIndex calls
- 1
- Last render
- 0 ms
0 ms16 ms frame32 ms
useState(() => buildSearchIndex(items))function FilteredList({ items }) {
const [index] = useState(() => buildSearchIndex(items))
const [query, setQuery] = useState('')
return <SearchResults index={index} query={query} />
}
Cache repeated calls in a Map
Press Play, then drag Unique names.
slugify() in the mapBad
- This render
- 0
- Total calls
- 0
- Renders
- 0
slugify(project.name)cachedSlugify()Good
- This render
- 0
- Total calls
- 0
- Cache size
- 0
slugifyCache.has(text)const slugifyCache = new Map()
export function cachedSlugify(text) {
if (slugifyCache.has(text)) return slugifyCache.get(text)
const result = slugify(text)
slugifyCache.set(text, result)
return result
}
Memoize patterns per query
Type c++ or (draft, then turn on re-renders.
new RegExp in renderBad
- new RegExp calls
- 0
- Renders
- 0
new RegExp(`(${query})`, 'gi')useMemo on [query]Good
- new RegExp calls
- 0
- Renders
- 0
escapeRegex(query)const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
function Highlighter({ text, query }) {
const regex = useMemo(
() => new RegExp(`(${escapeRegex(query)})`, 'gi'),
[query]
)
const parts = text.split(regex)
return parts.map((part, i) => (i % 2 ? <mark key={i}>{part}</mark> : part))
}
Why it matters, pros and cons
Why it matters
Arguments run before the call, so useState(build()) rebuilds on every render.
Pros
- Keystroke cost stops scaling with data
- A module Map works outside components too
- Escaping the query also fixes crashes
Cons
- Cheap initial values need no function form
- A lazy initializer never rebuilds when
itemschanges - Module Maps are unbounded and shared on the server