0
# Utilities
1
2
Utility functions for type checking and validation within the Redux ecosystem. These functions help ensure data integrity and provide type guards for working with Redux actions and plain objects.
3
4
## Capabilities
5
6
### Action Validation
7
8
Type guard function to check if a value is a valid Redux action.
9
10
```typescript { .api }
11
/**
12
* Type guard to check if a value is a valid Redux action
13
* @param action - The value to check
14
* @returns True if the value is a valid Redux action with string type property
15
*/
16
function isAction(action: unknown): action is Action<string>;
17
```
18
19
**Usage Examples:**
20
21
```typescript
22
import { isAction } from "redux";
23
24
// Basic usage
25
const maybeAction = { type: "INCREMENT" };
26
if (isAction(maybeAction)) {
27
// TypeScript now knows maybeAction is an Action<string>
28
console.log("Valid action:", maybeAction.type);
29
}
30
31
// Invalid actions
32
console.log(isAction({ type: 123 })); // false - type must be string
33
console.log(isAction({ data: "value" })); // false - missing type property
34
console.log(isAction(null)); // false
35
console.log(isAction("string")); // false
36
37
// Valid actions
38
console.log(isAction({ type: "INCREMENT" })); // true
39
console.log(isAction({ type: "ADD_TODO", payload: { text: "Learn Redux" } })); // true
40
41
// Use in middleware for validation
42
const validationMiddleware = (store) => (next) => (action) => {
43
if (!isAction(action)) {
44
console.error("Invalid action dispatched:", action);
45
return; // Don't process invalid actions
46
}
47
return next(action);
48
};
49
50
// Use in action processing
51
const processAction = (maybeAction: unknown) => {
52
if (isAction(maybeAction)) {
53
switch (maybeAction.type) {
54
case "INCREMENT":
55
// Handle increment
56
break;
57
case "DECREMENT":
58
// Handle decrement
59
break;
60
}
61
}
62
};
63
64
// Filter actions from mixed array
65
const mixedArray: unknown[] = [
66
{ type: "VALID_ACTION" },
67
{ notAnAction: true },
68
{ type: "ANOTHER_VALID_ACTION", payload: "data" },
69
"not an object",
70
{ type: 123 } // invalid - type is not string
71
];
72
73
const validActions = mixedArray.filter(isAction);
74
// validActions will contain only the valid Redux actions
75
```
76
77
### Plain Object Validation
78
79
Checks if an object is a plain object (created by Object literal or Object constructor).
80
81
```typescript { .api }
82
/**
83
* Checks if an object is a plain object
84
* @param obj - The object to inspect
85
* @returns True if the argument appears to be a plain object
86
*/
87
function isPlainObject(obj: any): obj is object;
88
```
89
90
**Usage Examples:**
91
92
```typescript
93
import { isPlainObject } from "redux";
94
95
// Plain objects (return true)
96
console.log(isPlainObject({})); // true
97
console.log(isPlainObject({ key: "value" })); // true
98
console.log(isPlainObject(Object.create(null))); // true
99
console.log(isPlainObject(new Object())); // true
100
101
// Non-plain objects (return false)
102
console.log(isPlainObject([])); // false - arrays are not plain objects
103
console.log(isPlainObject(new Date())); // false - Date instances
104
console.log(isPlainObject(new RegExp(""))); // false - RegExp instances
105
console.log(isPlainObject(function() {})); // false - functions
106
console.log(isPlainObject("string")); // false - primitive values
107
console.log(isPlainObject(42)); // false - numbers
108
console.log(isPlainObject(null)); // false - null
109
console.log(isPlainObject(undefined)); // false - undefined
110
111
// Class instances (return false)
112
class MyClass {}
113
console.log(isPlainObject(new MyClass())); // false
114
115
// Use in Redux internals (this is how Redux uses it)
116
const validateAction = (action) => {
117
if (!isPlainObject(action)) {
118
throw new Error(
119
`Actions must be plain objects. Instead, the actual type was: '${typeof action}'. ` +
120
`You may need to add middleware to your store setup to handle dispatching other values.`
121
);
122
}
123
};
124
125
// Use in reducer validation
126
const validateState = (state) => {
127
if (state !== null && !isPlainObject(state)) {
128
console.warn("State should be a plain object for predictable behavior");
129
}
130
};
131
132
// Use in serialization checks
133
const canSerialize = (obj: unknown): boolean => {
134
if (obj === null || typeof obj !== "object") {
135
return true; // Primitives are serializable
136
}
137
138
if (!isPlainObject(obj)) {
139
return false; // Non-plain objects may not serialize properly
140
}
141
142
// Check all properties recursively
143
return Object.values(obj).every(canSerialize);
144
};
145
146
// Use in deep cloning utilities
147
const deepClone = (obj: any): any => {
148
if (!isPlainObject(obj)) {
149
return obj; // Return primitive or non-plain object as-is
150
}
151
152
const cloned: any = {};
153
for (const key in obj) {
154
if (obj.hasOwnProperty(key)) {
155
cloned[key] = deepClone(obj[key]);
156
}
157
}
158
return cloned;
159
};
160
```
161
162
## Internal Utilities
163
164
While not exported, Redux uses several internal utilities that are worth understanding:
165
166
### Action Types (Internal)
167
168
Redux uses internal action types for initialization and replacement:
169
170
```typescript { .api }
171
/**
172
* Private action types reserved by Redux
173
* Do not reference these action types directly in your code
174
*/
175
const __DO_NOT_USE__ActionTypes: {
176
readonly INIT: string;
177
readonly REPLACE: string;
178
readonly PROBE_UNKNOWN_ACTION: () => string;
179
};
180
```
181
182
**Understanding Internal Actions:**
183
184
```typescript
185
// These are used internally by Redux and should not be used in application code
186
// INIT - Dispatched when store is created to get initial state from reducers
187
// REPLACE - Dispatched when replaceReducer is called
188
// PROBE_UNKNOWN_ACTION - Used to test reducer behavior with unknown actions
189
190
// Your reducers should handle these gracefully:
191
const myReducer = (state = initialState, action) => {
192
switch (action.type) {
193
case "MY_ACTION":
194
return { ...state, /* changes */ };
195
default:
196
// This handles INIT, REPLACE, PROBE_UNKNOWN_ACTION, and other unknown actions
197
return state;
198
}
199
};
200
```
201
202
## Practical Applications
203
204
### Custom Validation Middleware
205
206
Combining utilities for comprehensive validation:
207
208
```typescript
209
const validationMiddleware = (store) => (next) => (action) => {
210
// Validate action structure
211
if (!isAction(action)) {
212
console.error("Invalid action structure:", action);
213
return;
214
}
215
216
// Validate action is plain object
217
if (!isPlainObject(action)) {
218
console.error("Action must be a plain object:", action);
219
return;
220
}
221
222
// Validate payload if present
223
if ("payload" in action && action.payload !== undefined) {
224
if (typeof action.payload === "object" && !isPlainObject(action.payload)) {
225
console.warn("Action payload should be a plain object for serializability:", action);
226
}
227
}
228
229
return next(action);
230
};
231
```
232
233
### State Validation Helper
234
235
Helper function to validate entire state tree:
236
237
```typescript
238
const validateStateTree = (state: any, path = "root"): boolean => {
239
if (state === null || state === undefined) {
240
return true; // null/undefined are valid state values
241
}
242
243
if (typeof state !== "object") {
244
return true; // Primitive values are fine
245
}
246
247
if (!isPlainObject(state)) {
248
console.warn(`Non-plain object found in state at ${path}:`, state);
249
return false;
250
}
251
252
// Recursively validate nested objects
253
for (const [key, value] of Object.entries(state)) {
254
if (!validateStateTree(value, `${path}.${key}`)) {
255
return false;
256
}
257
}
258
259
return true;
260
};
261
262
// Use in development
263
if (process.env.NODE_ENV === "development") {
264
store.subscribe(() => {
265
validateStateTree(store.getState());
266
});
267
}
268
```
269
270
### Serialization Utilities
271
272
Utilities for checking if state can be persisted:
273
274
```typescript
275
const isSerializable = (obj: any): boolean => {
276
if (obj === null || obj === undefined) return true;
277
278
const type = typeof obj;
279
if (["string", "number", "boolean"].includes(type)) return true;
280
281
if (type === "object") {
282
if (Array.isArray(obj)) {
283
return obj.every(isSerializable);
284
}
285
286
if (!isPlainObject(obj)) return false;
287
288
return Object.values(obj).every(isSerializable);
289
}
290
291
return false; // Functions, Symbols, etc. are not serializable
292
};
293
294
// Check before persisting to localStorage
295
const persistState = (state: any) => {
296
if (isSerializable(state)) {
297
localStorage.setItem("app-state", JSON.stringify(state));
298
} else {
299
console.warn("State contains non-serializable values, skipping persistence");
300
}
301
};
302
```