Skip to content

Commit pending hook state in options._render - #5187

Merged
JoviDeCroock merged 2 commits into
mainfrom
golf-hooks-bytes
Jul 31, 2026
Merged

Commit pending hook state in options._render#5187
JoviDeCroock merged 2 commits into
mainfrom
golf-hooks-bytes

Conversation

@JoviDeCroock

Copy link
Copy Markdown
Member

useReducer installed two closures on every hook-using component: a shouldComponentUpdate that both decided the bail-out and committed _nextValue -> _value, and a componentWillUpdate that existed only to run that same commit on the forced-update path, where core skips sCU. It did so by temporarily nulling prevScu and re-entering the sCU, smuggling a mutation through a predicate.

The commit belongs in neither. options._render runs immediately before every render, forced or not, and already ran this exact loop for the same-component re-render branch. Hoisting it out unconditionally makes componentWillUpdate unnecessary and leaves sCU a pure predicate.

Clearing _pendingArgs unconditionally is safe because options.diffed promotes them after every successful diff, so leftovers belong to a render that never committed and this render recomputes them. On bail-out _nextValue simply stays pending; the dispatcher already reads _nextValue[0] when present.

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

📊 Tachometer Benchmark Results

Summary

duration

  • create10k: unsure 🔍 -1% - +0% (-7.96ms - +0.75ms)
    preact-local vs preact-main
  • filter-list: unsure 🔍 -1% - +0% (-0.16ms - +0.08ms)
    preact-local vs preact-main
  • hydrate1k: unsure 🔍 -3% - +1% (-1.81ms - +0.55ms)
    preact-local vs preact-main
  • many-updates: unsure 🔍 -0% - +0% (-0.03ms - +0.02ms)
    preact-local vs preact-main
  • replace1k: unsure 🔍 -2% - +1% (-0.76ms - +0.38ms)
    preact-local vs preact-main
  • text-update: unsure 🔍 -4% - +3% (-0.07ms - +0.06ms)
    preact-local vs preact-main
  • todo: unsure 🔍 -1% - +1% (-0.24ms - +0.46ms)
    preact-local vs preact-main
  • update10th1k: unsure 🔍 -2% - +4% (-0.51ms - +1.39ms)
    preact-local vs preact-main

usedJSHeapSize

  • create10k: unsure 🔍 -0% - +0% (-0.03ms - +0.02ms)
    preact-local vs preact-main
  • filter-list: unsure 🔍 -0% - +0% (-0.00ms - +0.00ms)
    preact-local vs preact-main
  • hydrate1k: unsure 🔍 -2% - +5% (-0.12ms - +0.25ms)
    preact-local vs preact-main
  • many-updates: unsure 🔍 -0% - +0% (-0.00ms - +0.00ms)
    preact-local vs preact-main
  • replace1k: unsure 🔍 -0% - +0% (-0.01ms - +0.01ms)
    preact-local vs preact-main
  • text-update: unsure 🔍 -1% - +5% (-0.01ms - +0.05ms)
    preact-local vs preact-main
  • todo: unsure 🔍 -0% - +0% (-0.00ms - +0.00ms)
    preact-local vs preact-main
  • update10th1k: unsure 🔍 -0% - +0% (-0.01ms - +0.01ms)
    preact-local vs preact-main

Results

create10k

duration

VersionAvg timevs preact-localvs preact-main
preact-local685.88ms - 689.90ms-unsure 🔍
-1% - +0%
-7.96ms - +0.75ms
preact-main687.63ms - 695.36msunsure 🔍
-0% - +1%
-0.75ms - +7.96ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local19.23ms - 19.26ms-unsure 🔍
-0% - +0%
-0.03ms - +0.02ms
preact-main19.24ms - 19.27msunsure 🔍
-0% - +0%
-0.02ms - +0.03ms
-
filter-list

duration

VersionAvg timevs preact-localvs preact-main
preact-local16.54ms - 16.67ms-unsure 🔍
-1% - +0%
-0.16ms - +0.08ms
preact-main16.54ms - 16.75msunsure 🔍
-0% - +1%
-0.08ms - +0.16ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local1.57ms - 1.57ms-unsure 🔍
-0% - +0%
-0.00ms - +0.00ms
preact-main1.57ms - 1.57msunsure 🔍
-0% - +0%
-0.00ms - +0.00ms
-
hydrate1k

duration

VersionAvg timevs preact-localvs preact-main
preact-local52.72ms - 54.37ms-unsure 🔍
-3% - +1%
-1.81ms - +0.55ms
preact-main53.32ms - 55.01msunsure 🔍
-1% - +3%
-0.55ms - +1.81ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local5.02ms - 5.33ms-unsure 🔍
-2% - +5%
-0.12ms - +0.25ms
preact-main5.01ms - 5.21msunsure 🔍
-5% - +2%
-0.25ms - +0.12ms
-
many-updates

duration

VersionAvg timevs preact-localvs preact-main
preact-local16.56ms - 16.59ms-unsure 🔍
-0% - +0%
-0.03ms - +0.02ms
preact-main16.56ms - 16.59msunsure 🔍
-0% - +0%
-0.02ms - +0.03ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local3.75ms - 3.76ms-unsure 🔍
-0% - +0%
-0.00ms - +0.00ms
preact-main3.75ms - 3.76msunsure 🔍
-0% - +0%
-0.00ms - +0.00ms
-
replace1k
  • Browser: chrome-headless
  • Sample size: 110
  • Built by: CI #5769
  • Commit: fbd11d3

duration

VersionAvg timevs preact-localvs preact-main
preact-local44.72ms - 45.51ms-unsure 🔍
-2% - +1%
-0.76ms - +0.38ms
preact-main44.89ms - 45.71msunsure 🔍
-1% - +2%
-0.38ms - +0.76ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local3.04ms - 3.05ms-unsure 🔍
-0% - +0%
-0.01ms - +0.01ms
preact-main3.04ms - 3.05msunsure 🔍
-0% - +0%
-0.01ms - +0.01ms
-

run-warmup-0

VersionAvg timevs preact-localvs preact-main
preact-local20.53ms - 21.08ms-unsure 🔍
-2% - +2%
-0.44ms - +0.33ms
preact-main20.58ms - 21.13msunsure 🔍
-2% - +2%
-0.33ms - +0.44ms
-

run-warmup-1

VersionAvg timevs preact-localvs preact-main
preact-local23.83ms - 24.72ms-unsure 🔍
-1% - +4%
-0.20ms - +1.03ms
preact-main23.43ms - 24.28msunsure 🔍
-4% - +1%
-1.03ms - +0.20ms
-

run-warmup-2

VersionAvg timevs preact-localvs preact-main
preact-local23.94ms - 24.84ms-unsure 🔍
-3% - +2%
-0.69ms - +0.60ms
preact-main23.98ms - 24.90msunsure 🔍
-2% - +3%
-0.60ms - +0.69ms
-

run-warmup-3

VersionAvg timevs preact-localvs preact-main
preact-local19.58ms - 19.72ms-unsure 🔍
-1% - +0%
-0.15ms - +0.05ms
preact-main19.63ms - 19.78msunsure 🔍
-0% - +1%
-0.05ms - +0.15ms
-

run-warmup-4

VersionAvg timevs preact-localvs preact-main
preact-local21.99ms - 22.78ms-unsure 🔍
-2% - +3%
-0.53ms - +0.62ms
preact-main21.92ms - 22.76msunsure 🔍
-3% - +2%
-0.62ms - +0.53ms
-

run-final

VersionAvg timevs preact-localvs preact-main
preact-local15.75ms - 16.02ms-unsure 🔍
-1% - +1%
-0.15ms - +0.22ms
preact-main15.72ms - 15.98msunsure 🔍
-1% - +1%
-0.22ms - +0.15ms
-
text-update
  • Browser: chrome-headless
  • Sample size: 180
  • Built by: CI #5769
  • Commit: fbd11d3

duration

VersionAvg timevs preact-localvs preact-main
preact-local1.73ms - 1.82ms-unsure 🔍
-4% - +3%
-0.07ms - +0.06ms
preact-main1.74ms - 1.83msunsure 🔍
-3% - +4%
-0.06ms - +0.07ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local1.04ms - 1.09ms-unsure 🔍
-1% - +5%
-0.01ms - +0.05ms
preact-main1.03ms - 1.07msunsure 🔍
-5% - +1%
-0.05ms - +0.01ms
-
todo

duration

VersionAvg timevs preact-localvs preact-main
preact-local31.41ms - 32.00ms-unsure 🔍
-1% - +1%
-0.24ms - +0.46ms
preact-main31.40ms - 31.78msunsure 🔍
-1% - +1%
-0.46ms - +0.24ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local1.28ms - 1.28ms-unsure 🔍
-0% - +0%
-0.00ms - +0.00ms
preact-main1.28ms - 1.28msunsure 🔍
-0% - +0%
-0.00ms - +0.00ms
-
update10th1k

duration

VersionAvg timevs preact-localvs preact-main
preact-local32.87ms - 34.30ms-unsure 🔍
-2% - +4%
-0.51ms - +1.39ms
preact-main32.52ms - 33.78msunsure 🔍
-4% - +2%
-1.39ms - +0.51ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local2.98ms - 2.99ms-unsure 🔍
-0% - +0%
-0.01ms - +0.01ms
preact-main2.98ms - 2.99msunsure 🔍
-0% - +0%
-0.01ms - +0.01ms
-

tachometer-reporter-action v2 for CI

@github-actions

Copy link
Copy Markdown

Size Change: -56 B (-0.35%)

Total Size: 15.8 kB

📦 View Changed
Filename Size Change
hooks/dist/hooks.mjs 1.5 kB -56 B (-3.61%)
ℹ️ View Unchanged
Filename Size
compat/dist/compat.mjs 3.76 kB
debug/dist/debug.mjs 3.89 kB
devtools/dist/devtools.mjs 274 B
dist/preact.mjs 5 kB
jsx-runtime/dist/jsxRuntime.mjs 861 B
test-utils/dist/testUtils.mjs 473 B

compressed-size-action

`useReducer` installed two closures on every hook-using component: a
`shouldComponentUpdate` that both decided the bail-out and committed
`_nextValue` -> `_value`, and a `componentWillUpdate` that existed only to
run that same commit on the forced-update path, where core skips sCU. It did
so by temporarily nulling `prevScu` and re-entering the sCU, smuggling a
mutation through a predicate.

The commit belongs in neither. `options._render` runs immediately before
every render, forced or not, and already ran this exact loop for the
same-component re-render branch. Hoisting it out unconditionally makes
`componentWillUpdate` unnecessary and leaves sCU a pure predicate.

Clearing `_pendingArgs` unconditionally is safe because `options.diffed`
promotes them after every successful diff, so leftovers belong to a render
that never committed and this render recomputes them. On bail-out
`_nextValue` simply stays pending; the dispatcher already reads
`_nextValue[0]` when present.

hooks: 1387 -> 1331 B brotli (-4.0%). Also drops a closure per component
instance, and stops compat's UNSAFE_* accessor setter from running
`Object.defineProperty` on every hook component.

The forced-update path had no coverage, so add a test that drives it through
a context provider.
@coveralls

coveralls commented Jul 31, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 97.667% (-1.6%) from 99.291% — golf-hooks-bytes into main

Assigning `componentWillUpdate` on an instance was only ever exercised by
`useReducer` installing its own; with that gone the setter half of the
`UNSAFE_*` alias accessors has no caller in the suite. It is still a
supported path for user code, so test it on its own.
@JoviDeCroock
JoviDeCroock merged commit 2360911 into main Jul 31, 2026
13 checks passed
@JoviDeCroock
JoviDeCroock deleted the golf-hooks-bytes branch July 31, 2026 11:01
@JoviDeCroock JoviDeCroock mentioned this pull request Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants