RMD055 — A hook's props passed as a plain object
class Panel extends Component {
@state count = 1;
// ✗ the compiler refuses this, and `use()` throws if it arrives anyway
counter = this.use(Counter, { start: this.count });
}
A field initializer runs once, so an object written in one holds what was true at that moment and goes
on holding it for the life of the hook. start there is 1 forever: this.count moving to 7
changes the owner and reaches nothing inside Counter.
Pass a callback, and the props follow:
counter = this.use(Counter, () => ({ start: this.count }));
The callback is cached on the signals it reads, so it is re-run on a render where one of them moved and skipped on a render where none did.
Constants are written the same way, and cost the same. A callback that reads no signal is called
once, at mount, and never again, and the inline functions in it keep their identity across the owner's
renders — measured in core's PropsBagRuns.test.tsx. So there is no bag cheap enough for the shape to
be worth choosing.
A development build calls it more often than that, and keeps none of it: a second time at mount, so RMD022 can compare the two bags and catch a value that is not a function of state, and once per render of the owner, so RMD027 can check the cache has not gone stale. The hook is handed the first bag in every build.
It throws in every build, like a write to props (RMD004, RMD015): the alternative is a shipped bundle serving one stale value for the life of the page, silently. The report beside the throw is development-only, and it names the owner, the hook, and the keys the object carried.
The mistake cannot be found from inside use(), which is handed a finished object with no way to tell
{ start: this.count } from { start: 1 }. The FORM is the visible half, so the form is what the
framework holds you to.
Next
- All diagnostics — every code the framework can report.
- Checking your app — the faults proved from the source, before anything runs.