or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

context-utilities.mdcore-hooks.mdindex.mdutility-functions.md

utility-functions.mddocs/

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

```