React hooks and utilities for integrating XState finite state machines and statecharts into React applications
Utility functions and compatibility helpers provided by @xstate/react.
A shallow equality comparison utility function, useful for custom comparison in selectors.
function shallowEqual(objA: any, objB: any): boolean;objA: First object to compareobjB: Second object to comparetrue if the objects are shallowly equal, false otherwise.
import { useSelector, shallowEqual } from "@xstate/react";
function UserComponent({ actor }: { actor: Actor<any> }) {
// Use shallow equality for object comparisons
const userProfile = useSelector(
actor,
(state) => ({
name: state.context.user.name,
email: state.context.user.email,
avatar: state.context.user.avatar
}),
shallowEqual // Prevents re-renders when object content is the same
);
return (
<div>
<h1>{userProfile.name}</h1>
<p>{userProfile.email}</p>
<img src={userProfile.avatar} alt="Avatar" />
</div>
);
}useSelector for object valuesThe function performs the following checks:
true if both objects are the same referencefalse if either object is null/undefined while the other isn'tfalse if objects are different typesfalse if objects have different numbers of properties// Custom deep equality (for nested objects)
const deepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
// Custom comparison for arrays
const arrayEqual = (a, b) =>
Array.isArray(a) && Array.isArray(b) &&
a.length === b.length &&
a.every((item, index) => item === b[index]);
// Usage with custom comparisons
const data = useSelector(actor, selector, deepEqual);
const items = useSelector(actor, (state) => state.context.items, arrayEqual);Legacy hook that provides the same functionality as useActor but specifically typed for state machines. This hook is deprecated and will be removed in a future version.
/** @deprecated Use useActor instead */
function useMachine<TMachine extends AnyStateMachine>(
machine: TMachine,
options?: ActorOptions<TMachine> & {
[K in RequiredActorOptionsKeys<TMachine>]: unknown;
}
): [StateFrom<TMachine>, Actor<TMachine>['send'], Actor<TMachine>];machine: The XState state machineoptions: Optional actor configuration optionsA tuple containing:
state: Current machine statesend: Function to send eventsactorRef: The actor reference// OLD - using useMachine (deprecated)
import { useMachine } from "@xstate/react";
function OldComponent() {
const [state, send, actorRef] = useMachine(myMachine);
return <div>{state.value}</div>;
}
// NEW - using useActor (recommended)
import { useActor } from "@xstate/react";
function NewComponent() {
const [state, send, actorRef] = useActor(myMachine);
return <div>{state.value}</div>;
}useMachine to useActor in importsuseMachine(machine, options) with useActor(machine, options)The useMachine hook was deprecated because:
useActor works with all actor types (machines, promises, observables)// Compare by specific properties only
const compareById = (a, b) => a?.id === b?.id;
// Compare arrays by length
const compareArrayLength = (a, b) =>
Array.isArray(a) && Array.isArray(b) ? a.length === b.length : a === b;
// Compare objects by specific keys
const compareByKeys = (keys) => (a, b) =>
keys.every(key => a?.[key] === b?.[key]);
// Usage examples
const user = useSelector(actor, (state) => state.context.user, compareById);
const items = useSelector(actor, (state) => state.context.items, compareArrayLength);
const settings = useSelector(
actor,
(state) => state.context.settings,
compareByKeys(['theme', 'language'])
);// Memoize expensive selectors
const memoizedSelector = useMemo(
() => (state) => {
// Expensive computation
return state.context.data.filter(item =>
item.tags.some(tag => tag.includes(searchTerm))
);
},
[searchTerm] // Recreate selector when searchTerm changes
);
const filteredData = useSelector(actor, memoizedSelector, shallowEqual);function SafeComponent({ actor }: { actor: Actor<any> }) {
const error = useSelector(
actor,
(state) => state.context.error,
// Custom comparison to only re-render on error message changes
(a, b) => a?.message === b?.message
);
if (error) {
throw error; // Let error boundary handle it
}
// Normal component rendering...
}function PerformanceAwareComponent({ actor }: { actor: Actor<any> }) {
const renderCount = useRef(0);
renderCount.current++;
const data = useSelector(
actor,
(state) => {
console.log(`Selector called, render #${renderCount.current}`);
return state.context.data;
},
(a, b) => {
const isEqual = shallowEqual(a, b);
console.log(`Comparison result: ${isEqual}`);
return isEqual;
}
);
return <div>Render #{renderCount.current}: {data.length} items</div>;
}Install with Tessl CLI
npx tessl i tessl/npm-xstate--react