CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-jotai

Primitive and flexible state management for React applications

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

vanilla-utilities.mddocs/

Vanilla Utilities

Core utility functions providing advanced atom patterns and functionality. These utilities extend the basic atom functionality with common patterns like storage persistence, family management, async operations, and more.

Capabilities

Reset Utilities

RESET Constant

Special symbol used to reset atom values to their initial state.

const RESET: unique symbol;

atomWithReset Function

Creates a resettable atom that can be reset to its initial value using the RESET symbol.

/**
 * Creates a resettable atom that can be reset to initial value
 * @param initialValue - The initial value for the atom
 * @returns WritableAtom that accepts SetStateAction or RESET
 */
function atomWithReset<Value>(
  initialValue: Value
): WritableAtom<Value, [SetStateAction<Value> | typeof RESET], void>;

type SetStateActionWithReset<Value> = SetStateAction<Value> | typeof RESET;

Usage Examples:

import { atomWithReset, RESET } from "jotai/utils";

const countAtom = atomWithReset(0);

// In component
const [count, setCount] = useAtom(countAtom);

// Normal updates
setCount(5);
setCount((prev) => prev + 1);

// Reset to initial value
setCount(RESET);

Reducer Utilities

atomWithReducer Function

Creates an atom using the reducer pattern for state updates.

/**
 * Creates an atom using reducer pattern for state updates
 * @param initialValue - The initial value for the atom
 * @param reducer - Reducer function that takes current value and action
 * @returns WritableAtom that accepts actions for state updates
 */
function atomWithReducer<Value, Action>(
  initialValue: Value,
  reducer: (value: Value, action: Action) => Value
): WritableAtom<Value, [Action], void>;

Usage Examples:

import { atomWithReducer } from "jotai/utils";

type CountAction = 
  | { type: "increment" }
  | { type: "decrement" }
  | { type: "set"; value: number };

const countReducer = (count: number, action: CountAction) => {
  switch (action.type) {
    case "increment":
      return count + 1;
    case "decrement":
      return count - 1;
    case "set":
      return action.value;
    default:
      return count;
  }
};

const countAtom = atomWithReducer(0, countReducer);

// In component
const [count, dispatch] = useAtom(countAtom);

// Dispatch actions
dispatch({ type: "increment" });
dispatch({ type: "set", value: 10 });

Family Utilities

atomFamily Function

Creates atom families for dynamic atom creation based on parameters.

/**
 * Creates atom families for dynamic atom creation based on parameters
 * @param initializeAtom - Function to create atoms based on parameters
 * @param areEqual - Optional equality function for parameter comparison
 * @returns AtomFamily instance with parameter-based atom management
 */
function atomFamily<Param, AtomType>(
  initializeAtom: (param: Param) => AtomType,
  areEqual?: (a: Param, b: Param) => boolean
): AtomFamily<Param, AtomType>;

interface AtomFamily<Param, AtomType> {
  (param: Param): AtomType;
  getParams(): Param[];
  remove(param: Param): void;
  setShouldRemove(shouldRemove: (createdAt: number, param: Param) => boolean): void;
  unstable_listen(callback: (event: AtomFamilyEvent<Param>) => void): () => void;
}

type AtomFamilyEvent<Param> = 
  | { type: "CREATE"; param: Param }
  | { type: "REMOVE"; param: Param };

Usage Examples:

import { atomFamily } from "jotai/utils";

// Simple atom family
const todoAtomFamily = atomFamily((id: number) => 
  atom({ id, text: "", completed: false })
);

// Usage
const todo1Atom = todoAtomFamily(1);
const todo2Atom = todoAtomFamily(2);

// Custom equality function
const userAtomFamily = atomFamily(
  (user: { id: number; name: string }) => atom(user),
  (a, b) => a.id === b.id
);

// Family management
const allParams = todoAtomFamily.getParams(); // Get all created parameters
todoAtomFamily.remove(1); // Remove atom for parameter 1

// Auto-cleanup based on time
todoAtomFamily.setShouldRemove((createdAt, param) => {
  return Date.now() - createdAt > 60000; // Remove after 1 minute
});

Selection Utilities

selectAtom Function

Creates derived atoms with selector functions and optional equality comparison.

/**
 * Creates derived atom with selector function and optional equality comparison
 * @param anAtom - Source atom to select from
 * @param selector - Function to transform the atom value
 * @param equalityFn - Optional function to compare selected values
 * @returns Derived atom with selected value
 */
function selectAtom<Value, Slice>(
  anAtom: Atom<Value>,
  selector: (value: Value, prevSlice?: Slice) => Slice,
  equalityFn?: (a: Slice, b: Slice) => boolean
): Atom<Slice>;

Usage Examples:

import { selectAtom } from "jotai/utils";

const userAtom = atom({
  id: 1,
  name: "Alice",
  email: "alice@example.com",
  preferences: { theme: "dark", notifications: true }
});

// Select specific fields
const userNameAtom = selectAtom(userAtom, (user) => user.name);
const userPreferencesAtom = selectAtom(userAtom, (user) => user.preferences);

// With equality function to prevent unnecessary re-renders
const userDisplayAtom = selectAtom(
  userAtom,
  (user) => ({ name: user.name, email: user.email }),
  (a, b) => a.name === b.name && a.email === b.email
);

Immutability Utilities

freezeAtom Function

Makes atom values immutable using Object.freeze.

/**
 * Makes atom values immutable using Object.freeze
 * @param anAtom - Atom to make immutable
 * @returns Atom with frozen values
 */
function freezeAtom<AtomType>(anAtom: AtomType): AtomType;

freezeAtomCreator Function (Deprecated)

Creates immutable atoms from atom creator functions.

/**
 * @deprecated Use freezeAtom instead
 * Creates immutable atoms from atom creator functions
 */
function freezeAtomCreator<Args extends unknown[], AtomType>(
  createAtom: (...args: Args) => AtomType
): (...args: Args) => AtomType;

Usage Examples:

import { freezeAtom } from "jotai/utils";

const mutableDataAtom = atom({ count: 0, items: [] });
const immutableDataAtom = freezeAtom(mutableDataAtom);

// Values are frozen and cannot be mutated
const [data, setData] = useAtom(immutableDataAtom);
// data is frozen, cannot modify data.count directly

Array Utilities

splitAtom Function

Splits array atoms into individual item atoms for granular updates.

/**
 * Splits array atom into individual item atoms (read-only)
 * @param arrAtom - Array atom to split
 * @returns Atom containing array of individual item atoms
 */
function splitAtom<Item>(
  arrAtom: Atom<Item[]>
): Atom<Atom<Item>[]>;

/**
 * Splits writable array atom into individual writable item atoms
 * @param arrAtom - Writable array atom to split
 * @returns Atom containing array of writable item atoms with remove capability
 */
function splitAtom<Item>(
  arrAtom: WritableAtom<Item[], [SetStateAction<Item[]>], void>
): Atom<WritableAtom<Item, [SetStateAction<Item>], void>[]>;

/**
 * Splits array atom with custom key extraction for item identification
 * @param arrAtom - Array atom to split
 * @param keyExtractor - Function to extract unique keys from items
 * @returns Atom containing array of keyed item atoms
 */
function splitAtom<Item, Key>(
  arrAtom: WritableAtom<Item[], [SetStateAction<Item[]>], void>,
  keyExtractor: (item: Item) => Key
): Atom<WritableAtom<Item, [SetStateAction<Item>], void>[]>;

Usage Examples:

import { splitAtom } from "jotai/utils";

const todosAtom = atom([
  { id: 1, text: "Learn Jotai", completed: false },
  { id: 2, text: "Build app", completed: false }
]);

const todoAtomsAtom = splitAtom(todosAtom);

// In component
function TodoList() {
  const todoAtoms = useAtomValue(todoAtomsAtom);
  
  return (
    <div>
      {todoAtoms.map((todoAtom, index) => (
        <TodoItem key={index} todoAtom={todoAtom} />
      ))}
    </div>
  );
}

function TodoItem({ todoAtom }: { todoAtom: WritableAtom<Todo, [SetStateAction<Todo>], void> }) {
  const [todo, setTodo] = useAtom(todoAtom);
  
  return (
    <div>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={(e) => setTodo((prev) => ({ ...prev, completed: e.target.checked }))}
      />
      <span>{todo.text}</span>
    </div>
  );
}

Default Value Utilities

atomWithDefault Function

Creates atoms with computed default values.

/**
 * Creates atom with computed default value
 * @param getDefault - Function to compute the default value
 * @returns WritableAtom with computed default
 */
function atomWithDefault<Value>(
  getDefault: Read<Value>
): WritableAtom<Value, [SetStateAction<Value> | typeof RESET], void>;

Usage Examples:

import { atomWithDefault, RESET } from "jotai/utils";

const configAtom = atom({ apiUrl: "https://api.example.com", timeout: 5000 });

const apiUrlAtom = atomWithDefault((get) => get(configAtom).apiUrl);
const timeoutAtom = atomWithDefault((get) => get(configAtom).timeout);

// Can be used normally
const [apiUrl, setApiUrl] = useAtom(apiUrlAtom);
setApiUrl("https://api-staging.example.com");

// Reset to computed default
setApiUrl(RESET); // Recomputes from configAtom

Storage Utilities

atomWithStorage Function

Creates persistent atoms backed by storage.

/**
 * Creates persistent atom backed by synchronous storage
 * @param key - Storage key
 * @param initialValue - Initial value if not in storage
 * @param storage - Optional storage implementation
 * @returns WritableAtom with storage persistence
 */
function atomWithStorage<Value>(
  key: string,
  initialValue: Value,
  storage?: SyncStorage<Value>
): WritableAtom<Value, [SetStateAction<Value>], void>;

/**
 * Creates persistent atom backed by asynchronous storage
 * @param key - Storage key
 * @param initialValue - Initial value if not in storage
 * @param storage - Async storage implementation
 * @returns WritableAtom with async storage persistence
 */
function atomWithStorage<Value>(
  key: string,
  initialValue: Value,
  storage: AsyncStorage<Value>
): WritableAtom<Value, [SetStateAction<Value>], void>;

createJSONStorage Function

Creates JSON-based storage adapters.

/**
 * Creates default JSON storage adapter (uses localStorage)
 * @returns JSON storage adapter using localStorage
 */
function createJSONStorage<Value>(): SyncStorage<Value>;

/**
 * Creates sync JSON storage adapter from string storage
 * @param getStringStorage - Function returning sync string storage
 * @param options - Optional JSON parsing/stringifying options
 * @returns Sync JSON storage adapter
 */
function createJSONStorage<Value>(
  getStringStorage: () => SyncStringStorage,
  options?: JsonStorageOptions
): SyncStorage<Value>;

/**
 * Creates async JSON storage adapter from string storage
 * @param getStringStorage - Function returning async string storage
 * @param options - Optional JSON parsing/stringifying options
 * @returns Async JSON storage adapter
 */
function createJSONStorage<Value>(
  getStringStorage: () => AsyncStringStorage,
  options?: JsonStorageOptions
): AsyncStorage<Value>;

withStorageValidator Function (unstable)

Adds validation to storage operations. Exported as unstable_withStorageValidator.

/**
 * Adds validation to storage operations
 * @param validator - Type guard function for validation
 * @returns Function that wraps storage with validation
 */
function withStorageValidator<Value>(
  validator: (value: unknown) => value is Value
): <S extends SyncStorage<any> | AsyncStorage<any>>(
  storage: S
) => S extends SyncStorage<any> ? SyncStorage<Value> : AsyncStorage<Value>;

Usage Examples:

import { atomWithStorage, createJSONStorage, unstable_withStorageValidator } from "jotai/utils";

// Basic localStorage usage
const darkModeAtom = atomWithStorage("darkMode", false);

// Custom storage
const customStorage = createJSONStorage(() => sessionStorage);
const sessionAtom = atomWithStorage("session", null, customStorage);

// With validation
const isUser = (value: unknown): value is { id: number; name: string } =>
  typeof value === "object" && value !== null &&
  typeof (value as any).id === "number" &&
  typeof (value as any).name === "string";

const validatedStorage = unstable_withStorageValidator(isUser)(
  createJSONStorage(() => localStorage)
);

const userAtom = atomWithStorage("user", { id: 0, name: "" }, validatedStorage);

Storage Interfaces

type Subscribe<Value> = (
  key: string,
  callback: (value: Value) => void,
  initialValue: Value
) => (() => void) | undefined;

type StringSubscribe = (
  key: string,
  callback: (value: string | null) => void
) => (() => void) | undefined;

interface SyncStorage<Value> {
  getItem: (key: string, initialValue: Value) => Value;
  setItem: (key: string, value: Value) => void;
  removeItem: (key: string) => void;
  subscribe?: Subscribe<Value>;
}

interface AsyncStorage<Value> {
  getItem: (key: string, initialValue: Value) => Promise<Value>;
  setItem: (key: string, value: Value) => Promise<void>;
  removeItem: (key: string) => Promise<void>;
  subscribe?: Subscribe<Value>;
}

interface SyncStringStorage {
  getItem: (key: string) => string | null;
  setItem: (key: string, value: string) => void;
  removeItem: (key: string) => void;
  subscribe?: StringSubscribe;
}

interface AsyncStringStorage {
  getItem: (key: string) => Promise<string | null>;
  setItem: (key: string, value: string) => Promise<void>;
  removeItem: (key: string) => Promise<void>;
  subscribe?: StringSubscribe;
}

type JsonStorageOptions = {
  reviver?: (key: string, value: unknown) => unknown;
  replacer?: (key: string, value: unknown) => unknown;
};

Observable Utilities

atomWithObservable Function

Creates atoms from observables (RxJS, etc.).

/**
 * Creates atom from observable
 * @param createObservable - Function that creates observable
 * @returns Atom that follows observable values
 */
function atomWithObservable<Value>(
  createObservable: () => Observable<Value>
): Atom<Value>;

/**
 * Creates atom from observable with initial value
 * @param createObservable - Function that creates observable
 * @param initialValue - Initial value before observable emits
 * @returns Atom that follows observable values
 */
function atomWithObservable<Value>(
  createObservable: () => Observable<Value>,
  initialValue: Value
): Atom<Value>;

interface Observable<Value> {
  subscribe(observer: {
    next: (value: Value) => void;
    error?: (error: any) => void;
    complete?: () => void;
  }): { unsubscribe: () => void };
}

Usage Examples:

import { atomWithObservable } from "jotai/utils";
import { interval } from "rxjs";
import { map } from "rxjs/operators";

// Simple observable atom
const clockAtom = atomWithObservable(() => 
  interval(1000).pipe(map(() => new Date()))
);

// With initial value
const countdownAtom = atomWithObservable(
  () => interval(1000).pipe(map(i => 10 - i)),
  10
);

Async Utilities

loadable Function

Wraps atoms in loadable state for async operations.

/**
 * Wraps atom in loadable state for async operations
 * @param anAtom - Atom to wrap
 * @returns Atom with loadable state
 */
function loadable<Value>(anAtom: Atom<Value>): Atom<Loadable<Value>>;

type Loadable<Value> =
  | { state: "loading" }
  | { state: "hasError"; error: unknown }
  | { state: "hasData"; data: Value };

unwrap Function

Unwraps promise-based atoms with optional fallback values.

/**
 * Unwraps promise-based atom with fallback value
 * @param anAtom - Promise-based atom to unwrap
 * @param fallback - Fallback value while promise is pending
 * @returns Atom with unwrapped value
 */
function unwrap<Value>(
  anAtom: Atom<Promise<Value>>,
  fallback: Value
): Atom<Value>;

/**
 * Unwraps promise-based atom, throwing on pending
 * @param anAtom - Promise-based atom to unwrap  
 * @returns Atom with unwrapped value
 */
function unwrap<Value>(anAtom: Atom<Promise<Value>>): Atom<Value>;

Usage Examples:

import { loadable, unwrap } from "jotai/utils";

const asyncDataAtom = atom(async () => {
  const response = await fetch("/api/data");
  return response.json();
});

// Using loadable
const loadableDataAtom = loadable(asyncDataAtom);

function DataComponent() {
  const loadableData = useAtomValue(loadableDataAtom);
  
  if (loadableData.state === "loading") {
    return <div>Loading...</div>;
  }
  
  if (loadableData.state === "hasError") {
    return <div>Error: {loadableData.error.message}</div>;
  }
  
  return <div>Data: {JSON.stringify(loadableData.data)}</div>;
}

// Using unwrap with fallback
const unwrappedDataAtom = unwrap(asyncDataAtom, []);

function SimpleDataComponent() {
  const data = useAtomValue(unwrappedDataAtom);
  return <div>Data: {JSON.stringify(data)}</div>;
}

Refresh Utilities

atomWithRefresh Function

Creates refreshable atoms that can be manually refreshed by calling the setter with no arguments.

/**
 * Creates refreshable read-only atom
 * @param read - Read function for the atom
 * @returns WritableAtom that refreshes when called with no arguments
 */
function atomWithRefresh<Value>(
  read: Read<Value, [], void>
): WritableAtom<Value, [], void>;

/**
 * Creates refreshable writable atom
 * @param read - Read function for the atom
 * @param write - Write function for the atom
 * @returns WritableAtom that refreshes when called with no arguments, otherwise uses write function
 */
function atomWithRefresh<Value, Args extends unknown[], Result>(
  read: Read<Value, Args, Result>,
  write: Write<Value, Args, Result>
): WritableAtom<Value, Args | [], Result | void>;

Usage Examples:

import { atomWithRefresh } from "jotai/utils";

const dataAtom = atomWithRefresh(async () => {
  const response = await fetch("/api/data");
  return response.json();
});

function DataComponent() {
  const data = useAtomValue(dataAtom);
  const refreshData = useSetAtom(dataAtom);
  
  return (
    <div>
      <div>Data: {JSON.stringify(data)}</div>
      <button onClick={() => refreshData()}>Refresh</button>
    </div>
  );
}

// Writable refreshable atom
const userAtom = atomWithRefresh(
  async () => {
    const response = await fetch("/api/user");
    return response.json();
  },
  async (get, set, newUser: User) => {
    await fetch("/api/user", {
      method: "PUT",
      body: JSON.stringify(newUser)
    });
    // Refresh after update
    set(userAtom);
  }
);

function UserComponent() {
  const [user, setUser] = useAtom(userAtom);
  
  return (
    <div>
      <div>User: {user.name}</div>
      <button onClick={() => setUser()}>Refresh User</button>
      <button onClick={() => setUser({ ...user, name: "New Name" })}>
        Update User
      </button>
    </div>
  );
}

Lazy Utilities

atomWithLazy Function

Creates lazily initialized atoms.

/**
 * Creates lazily initialized atom
 * @param makeInitial - Function to create initial value
 * @returns PrimitiveAtom with lazy initialization
 */
function atomWithLazy<Value>(makeInitial: () => Value): PrimitiveAtom<Value>;

Usage Examples:

import { atomWithLazy } from "jotai/utils";

// Expensive computation only runs when first accessed
const expensiveAtom = atomWithLazy(() => {
  console.log("Computing expensive value...");
  return Array.from({ length: 1000000 }, (_, i) => i);
});

// Initial value is computed lazily
const timestampAtom = atomWithLazy(() => Date.now());

docs

core-atoms.md

index.md

react-integration.md

react-utilities.md

vanilla-utilities.md

tile.json