CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-redux

Predictable state container for JavaScript apps

Pending
Overview
Eval results
Files

utilities.mddocs/

Utilities

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.

Capabilities

Action Validation

Type guard function to check if a value is a valid Redux action.

/**
 * Type guard to check if a value is a valid Redux action
 * @param action - The value to check
 * @returns True if the value is a valid Redux action with string type property
 */
function isAction(action: unknown): action is Action<string>;

Usage Examples:

import { isAction } from "redux";

// Basic usage
const maybeAction = { type: "INCREMENT" };
if (isAction(maybeAction)) {
  // TypeScript now knows maybeAction is an Action<string>
  console.log("Valid action:", maybeAction.type);
}

// Invalid actions
console.log(isAction({ type: 123 })); // false - type must be string
console.log(isAction({ data: "value" })); // false - missing type property
console.log(isAction(null)); // false
console.log(isAction("string")); // false

// Valid actions
console.log(isAction({ type: "INCREMENT" })); // true
console.log(isAction({ type: "ADD_TODO", payload: { text: "Learn Redux" } })); // true

// Use in middleware for validation
const validationMiddleware = (store) => (next) => (action) => {
  if (!isAction(action)) {
    console.error("Invalid action dispatched:", action);
    return; // Don't process invalid actions
  }
  return next(action);
};

// Use in action processing
const processAction = (maybeAction: unknown) => {
  if (isAction(maybeAction)) {
    switch (maybeAction.type) {
      case "INCREMENT":
        // Handle increment
        break;
      case "DECREMENT":
        // Handle decrement
        break;
    }
  }
};

// Filter actions from mixed array
const mixedArray: unknown[] = [
  { type: "VALID_ACTION" },
  { notAnAction: true },
  { type: "ANOTHER_VALID_ACTION", payload: "data" },
  "not an object",
  { type: 123 } // invalid - type is not string
];

const validActions = mixedArray.filter(isAction);
// validActions will contain only the valid Redux actions

Plain Object Validation

Checks if an object is a plain object (created by Object literal or Object constructor).

/**
 * Checks if an object is a plain object
 * @param obj - The object to inspect
 * @returns True if the argument appears to be a plain object
 */
function isPlainObject(obj: any): obj is object;

Usage Examples:

import { isPlainObject } from "redux";

// Plain objects (return true)
console.log(isPlainObject({})); // true
console.log(isPlainObject({ key: "value" })); // true
console.log(isPlainObject(Object.create(null))); // true
console.log(isPlainObject(new Object())); // true

// Non-plain objects (return false)
console.log(isPlainObject([])); // false - arrays are not plain objects
console.log(isPlainObject(new Date())); // false - Date instances
console.log(isPlainObject(new RegExp(""))); // false - RegExp instances
console.log(isPlainObject(function() {})); // false - functions
console.log(isPlainObject("string")); // false - primitive values
console.log(isPlainObject(42)); // false - numbers
console.log(isPlainObject(null)); // false - null
console.log(isPlainObject(undefined)); // false - undefined

// Class instances (return false)
class MyClass {}
console.log(isPlainObject(new MyClass())); // false

// Use in Redux internals (this is how Redux uses it)
const validateAction = (action) => {
  if (!isPlainObject(action)) {
    throw new Error(
      `Actions must be plain objects. Instead, the actual type was: '${typeof action}'. ` +
      `You may need to add middleware to your store setup to handle dispatching other values.`
    );
  }
};

// Use in reducer validation
const validateState = (state) => {
  if (state !== null && !isPlainObject(state)) {
    console.warn("State should be a plain object for predictable behavior");
  }
};

// Use in serialization checks
const canSerialize = (obj: unknown): boolean => {
  if (obj === null || typeof obj !== "object") {
    return true; // Primitives are serializable
  }
  
  if (!isPlainObject(obj)) {
    return false; // Non-plain objects may not serialize properly
  }
  
  // Check all properties recursively
  return Object.values(obj).every(canSerialize);
};

// Use in deep cloning utilities
const deepClone = (obj: any): any => {
  if (!isPlainObject(obj)) {
    return obj; // Return primitive or non-plain object as-is
  }
  
  const cloned: any = {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloned[key] = deepClone(obj[key]);
    }
  }
  return cloned;
};

Internal Utilities

While not exported, Redux uses several internal utilities that are worth understanding:

Action Types (Internal)

Redux uses internal action types for initialization and replacement:

/**
 * Private action types reserved by Redux
 * Do not reference these action types directly in your code
 */
const __DO_NOT_USE__ActionTypes: {
  readonly INIT: string;
  readonly REPLACE: string;
  readonly PROBE_UNKNOWN_ACTION: () => string;
};

Understanding Internal Actions:

// These are used internally by Redux and should not be used in application code
// INIT - Dispatched when store is created to get initial state from reducers
// REPLACE - Dispatched when replaceReducer is called
// PROBE_UNKNOWN_ACTION - Used to test reducer behavior with unknown actions

// Your reducers should handle these gracefully:
const myReducer = (state = initialState, action) => {
  switch (action.type) {
    case "MY_ACTION":
      return { ...state, /* changes */ };
    default:
      // This handles INIT, REPLACE, PROBE_UNKNOWN_ACTION, and other unknown actions
      return state;
  }
};

Practical Applications

Custom Validation Middleware

Combining utilities for comprehensive validation:

const validationMiddleware = (store) => (next) => (action) => {
  // Validate action structure
  if (!isAction(action)) {
    console.error("Invalid action structure:", action);
    return;
  }
  
  // Validate action is plain object
  if (!isPlainObject(action)) {
    console.error("Action must be a plain object:", action);
    return;
  }
  
  // Validate payload if present
  if ("payload" in action && action.payload !== undefined) {
    if (typeof action.payload === "object" && !isPlainObject(action.payload)) {
      console.warn("Action payload should be a plain object for serializability:", action);
    }
  }
  
  return next(action);
};

State Validation Helper

Helper function to validate entire state tree:

const validateStateTree = (state: any, path = "root"): boolean => {
  if (state === null || state === undefined) {
    return true; // null/undefined are valid state values
  }
  
  if (typeof state !== "object") {
    return true; // Primitive values are fine
  }
  
  if (!isPlainObject(state)) {
    console.warn(`Non-plain object found in state at ${path}:`, state);
    return false;
  }
  
  // Recursively validate nested objects
  for (const [key, value] of Object.entries(state)) {
    if (!validateStateTree(value, `${path}.${key}`)) {
      return false;
    }
  }
  
  return true;
};

// Use in development
if (process.env.NODE_ENV === "development") {
  store.subscribe(() => {
    validateStateTree(store.getState());
  });
}

Serialization Utilities

Utilities for checking if state can be persisted:

const isSerializable = (obj: any): boolean => {
  if (obj === null || obj === undefined) return true;
  
  const type = typeof obj;
  if (["string", "number", "boolean"].includes(type)) return true;
  
  if (type === "object") {
    if (Array.isArray(obj)) {
      return obj.every(isSerializable);
    }
    
    if (!isPlainObject(obj)) return false;
    
    return Object.values(obj).every(isSerializable);
  }
  
  return false; // Functions, Symbols, etc. are not serializable
};

// Check before persisting to localStorage
const persistState = (state: any) => {
  if (isSerializable(state)) {
    localStorage.setItem("app-state", JSON.stringify(state));
  } else {
    console.warn("State contains non-serializable values, skipping persistence");
  }
};

Install with Tessl CLI

npx tessl i tessl/npm-redux

docs

actions.md

index.md

middleware.md

reducer-composition.md

store-management.md

utilities.md

tile.json