0
# Utility Functions
1
2
Utility functions and compatibility helpers provided by @xstate/react.
3
4
## shallowEqual
5
6
A shallow equality comparison utility function, useful for custom comparison in selectors.
7
8
```typescript { .api }
9
function shallowEqual(objA: any, objB: any): boolean;
10
```
11
12
### Parameters
13
14
- `objA`: First object to compare
15
- `objB`: Second object to compare
16
17
### Returns
18
19
`true` if the objects are shallowly equal, `false` otherwise.
20
21
### Usage Example
22
23
```typescript
24
import { useSelector, shallowEqual } from "@xstate/react";
25
26
function UserComponent({ actor }: { actor: Actor<any> }) {
27
// Use shallow equality for object comparisons
28
const userProfile = useSelector(
29
actor,
30
(state) => ({
31
name: state.context.user.name,
32
email: state.context.user.email,
33
avatar: state.context.user.avatar
34
}),
35
shallowEqual // Prevents re-renders when object content is the same
36
);
37
38
return (
39
<div>
40
<h1>{userProfile.name}</h1>
41
<p>{userProfile.email}</p>
42
<img src={userProfile.avatar} alt="Avatar" />
43
</div>
44
);
45
}
46
```
47
48
### Use Cases
49
50
- Custom comparison in `useSelector` for object values
51
- Preventing unnecessary re-renders when object structure is stable
52
- Compatible with Redux patterns (based on react-redux implementation)
53
54
### Implementation Details
55
56
The function performs the following checks:
57
58
1. **Reference equality**: Returns `true` if both objects are the same reference
59
2. **Null/undefined handling**: Returns `false` if either object is null/undefined while the other isn't
60
3. **Type checking**: Returns `false` if objects are different types
61
4. **Key comparison**: Compares all enumerable properties shallowly
62
5. **Property count**: Returns `false` if objects have different numbers of properties
63
64
### Alternative Comparison Functions
65
66
```typescript
67
// Custom deep equality (for nested objects)
68
const deepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
69
70
// Custom comparison for arrays
71
const arrayEqual = (a, b) =>
72
Array.isArray(a) && Array.isArray(b) &&
73
a.length === b.length &&
74
a.every((item, index) => item === b[index]);
75
76
// Usage with custom comparisons
77
const data = useSelector(actor, selector, deepEqual);
78
const items = useSelector(actor, (state) => state.context.items, arrayEqual);
79
```
80
81
## useMachine (Deprecated)
82
83
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.
84
85
```typescript { .api }
86
/** @deprecated Use useActor instead */
87
function useMachine<TMachine extends AnyStateMachine>(
88
machine: TMachine,
89
options?: ActorOptions<TMachine> & {
90
[K in RequiredActorOptionsKeys<TMachine>]: unknown;
91
}
92
): [StateFrom<TMachine>, Actor<TMachine>['send'], Actor<TMachine>];
93
```
94
95
### Parameters
96
97
- `machine`: The XState state machine
98
- `options`: Optional actor configuration options
99
100
### Returns
101
102
A tuple containing:
103
- `state`: Current machine state
104
- `send`: Function to send events
105
- `actorRef`: The actor reference
106
107
### Migration Example
108
109
```typescript
110
// OLD - using useMachine (deprecated)
111
import { useMachine } from "@xstate/react";
112
113
function OldComponent() {
114
const [state, send, actorRef] = useMachine(myMachine);
115
return <div>{state.value}</div>;
116
}
117
118
// NEW - using useActor (recommended)
119
import { useActor } from "@xstate/react";
120
121
function NewComponent() {
122
const [state, send, actorRef] = useActor(myMachine);
123
return <div>{state.value}</div>;
124
}
125
```
126
127
### Migration Guide
128
129
1. **Replace import**: Change `useMachine` to `useActor` in imports
130
2. **Update function calls**: Replace `useMachine(machine, options)` with `useActor(machine, options)`
131
3. **Type updates**: Return types are identical, no type changes needed
132
4. **Functionality**: Behavior is identical - this is a direct alias
133
134
### Why Deprecated?
135
136
The `useMachine` hook was deprecated because:
137
138
- **Consistency**: `useActor` works with all actor types (machines, promises, observables)
139
- **Simplification**: Reduces API surface area
140
- **Future-proofing**: Aligns with XState v5's unified actor model
141
142
## Common Utility Patterns
143
144
### Custom Comparison Functions
145
146
```typescript
147
// Compare by specific properties only
148
const compareById = (a, b) => a?.id === b?.id;
149
150
// Compare arrays by length
151
const compareArrayLength = (a, b) =>
152
Array.isArray(a) && Array.isArray(b) ? a.length === b.length : a === b;
153
154
// Compare objects by specific keys
155
const compareByKeys = (keys) => (a, b) =>
156
keys.every(key => a?.[key] === b?.[key]);
157
158
// Usage examples
159
const user = useSelector(actor, (state) => state.context.user, compareById);
160
const items = useSelector(actor, (state) => state.context.items, compareArrayLength);
161
const settings = useSelector(
162
actor,
163
(state) => state.context.settings,
164
compareByKeys(['theme', 'language'])
165
);
166
```
167
168
### Selector Optimization
169
170
```typescript
171
// Memoize expensive selectors
172
const memoizedSelector = useMemo(
173
() => (state) => {
174
// Expensive computation
175
return state.context.data.filter(item =>
176
item.tags.some(tag => tag.includes(searchTerm))
177
);
178
},
179
[searchTerm] // Recreate selector when searchTerm changes
180
);
181
182
const filteredData = useSelector(actor, memoizedSelector, shallowEqual);
183
```
184
185
### Error Boundary Integration
186
187
```typescript
188
function SafeComponent({ actor }: { actor: Actor<any> }) {
189
const error = useSelector(
190
actor,
191
(state) => state.context.error,
192
// Custom comparison to only re-render on error message changes
193
(a, b) => a?.message === b?.message
194
);
195
196
if (error) {
197
throw error; // Let error boundary handle it
198
}
199
200
// Normal component rendering...
201
}
202
```
203
204
### Performance Monitoring
205
206
```typescript
207
function PerformanceAwareComponent({ actor }: { actor: Actor<any> }) {
208
const renderCount = useRef(0);
209
renderCount.current++;
210
211
const data = useSelector(
212
actor,
213
(state) => {
214
console.log(`Selector called, render #${renderCount.current}`);
215
return state.context.data;
216
},
217
(a, b) => {
218
const isEqual = shallowEqual(a, b);
219
console.log(`Comparison result: ${isEqual}`);
220
return isEqual;
221
}
222
);
223
224
return <div>Render #{renderCount.current}: {data.length} items</div>;
225
}
226
```