Overview
Overview¶
Two rules make ReactPy predictable: components and hooks must be pure, and ReactPy must be the one that calls them. They look like style advice. They are load-bearing — and in ReactPy, one of them is not enforced by anything except you.
| Rule | In one line | Read |
|---|---|---|
| Purity | A component or hook returns the same output for the same inputs, and changes nothing outside itself. | Components and Hooks must be pure |
| Who calls what | You write components and hooks; ReactPy calls them — always from the top level of a component, in the same order. | React calls Components and Hooks, Rules of Hooks |
The two are one idea seen from either side. ReactPy is free to call your component whenever it likes, as often as it likes, because purity makes that safe. In return, your component gets to assume nothing about when it ran — which is why state lives in hooks rather than in variables.
Why the rules exist¶
ReactPy renders on a server, inside one Python process that serves many connections, and re-renders a component whenever its state changes or its parent renders. Both facts are what the rules protect against.
- Impure rendering leaks between users. A module-level
dictwritten during a render is shared by every connection in the process. Three visitors, one cart. No error, and each page looks fine. Measured in Components and Hooks must be pure. - Render frequency is not yours to predict. Code that counts, accumulates, or logs in a component body runs more often than the user acts. A child re-renders every time its parent does — ReactPy has no
memobailout for components. - Hook state is positional. ReactPy hands out hook values from a list by index, so changing which hooks run changes which value each hook reads. The usual outcome is not an error but a component quietly reading its neighbour's state. See Rules of Hooks.
The last point is the one that surprises people coming from React. React's hook dispatcher validates call order and throws; ReactPy's does not. Several violations are tolerated silently, and one of them corrupts state with nothing logged anywhere. The rules are still exactly React's — the safety net underneath them is not.
The rules, as a checklist¶
For a component body:
- reads only its arguments, and values from its own hooks
- assigns no global or module-level name
- mutates no object it did not create during this render
- reads no clock,
random, environment, or I/O - starts no task, thread, or timer
- may freely define event handlers — they run later, and may do all of the above
For a hook call site:
- called from a component body or another hook — never an event handler, effect body, or plain function
- at the top level: not under
if,for,try, or a nesteddef - the same hooks, in the same order, on every render
- no early
returnbefore a hook
For a custom hook (use_*): everything above, plus it must not skip its own hooks on some calls.
What ReactPy cannot check for you¶
There is no equivalent of eslint-plugin-react-hooks here, and no runtime assertion. Two settings get you most of the way:
REACTPY_DEBUG=1turns a render failure into a visible error in the model instead of a blank region, and enables the VDOM checks described in Common Props.- Tests are cheap precisely because of the rules: a pure component is an ordinary function — call it inside
reactpy.core.layout.Layout, render it twice, assert the models are equal. No browser involved.
Everything else is review. The greppable version: any use_ call that appears below the first non-hook statement of a component body, or inside a conditional, loop, or nested function, is a defect until proven otherwise.
At a glance¶
| You might be tempted to | Instead |
|---|---|
Cache a computation in a module-level dict | use_memo |
| Keep a per-user counter in a global | use_state or use_reducer |
| Fetch data in the component body | An event handler, or use_async_effect |
Fetch request headers with os.environ | use_scope / use_location |
| Call a hook from an event handler | Call it in the body and close over the value |
| Skip a hook when a prop is falsy | Return early only after all hooks, or split into two components |
Loop over use_state | One component per item, keyed |
Reach for use_effect to derive a value | Compute it in the body |
Related¶
- Components and Hooks must be pure — the contract, the server-shared-state leak, where effects belong
- React calls Components and Hooks — constructors versus calls, reserved
key, where hooks may run - Rules of Hooks — slots, and the measured cost of breaking order
- Keeping Components Pure — the tutorial version, with exercises
- Your First Component — start here if you have not written one yet