-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
Context
With React 19 and React Compiler, manual memoization via useMemo and useCallback is no longer recommended - the compiler automatically optimizes re-renders. However, @xstate/react's useActor hook compares logic.config by reference, which means machines created via factory functions (like setup().createMachine()) must still be wrapped in useMemo to prevent infinite re-render loops.
The Problem
When using a factory function to create a machine with dynamic config (e.g., passing wagmi config):
// This causes an infinite loop without useMemo
function MyComponent() {
const config = useConfig();
const machine = createMyMachine(config); // New reference each render
const [state] = useActor(machine); // useActor detects "new" machine → setCurrent → re-render → loop
}The workaround requires explicit memoization:
function MyComponent() {
const config = useConfig();
// Must memoize to prevent infinite loop
const machine = useMemo(() => createMyMachine(config), [config]);
const [state] = useActor(machine);
}This works, but conflicts with React Compiler's philosophy of automatic memoization.
Proposed Solution
Consider one of these approaches:
-
Deep equality check on machine config - Instead of reference equality on
logic.config, use a deep comparison or hash of the relevant config properties. -
Stable machine identity hook - Provide a
useStableMachineor similar that handles the memoization internally based on a user-provided dependency array. -
Document React Compiler compatibility - If this is intentional behavior, clearly document that
useMemois still required for factory-created machines even with React Compiler.
Environment
- XState v5
@xstate/react- React 19 with React Compiler
Related Issues
- Allow useMachine to accept a new machine between renders #1101 - "Allow useMachine to accept a new machine between renders" (closed 2020)
- Why useConstant? #913 - "Why useConstant?" (closed 2019)