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

react-utilities.mddocs/

React Utilities

React-specific utility hooks providing advanced patterns and functionality for Jotai atoms in React applications. These utilities complement the core React integration with specialized hooks for common use cases.

Capabilities

Reset Utilities

useResetAtom Hook

Hook for resetting atoms created with atomWithReset.

/**
 * Hook for resetting atoms created with atomWithReset
 * @param anAtom - WritableAtom that accepts RESET
 * @param options - Optional configuration
 * @returns Reset function that resets the atom to its initial value
 */
function useResetAtom<T>(
  anAtom: WritableAtom<unknown, [typeof RESET], T>,
  options?: Options
): () => T;

interface Options {
  store?: Store;
}

Usage Examples:

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

const countAtom = atomWithReset(0);

function Counter() {
  const [count, setCount] = useAtom(countAtom);
  const resetCount = useResetAtom(countAtom);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>+</button>
      <button onClick={() => setCount((c) => c - 1)}>-</button>
      <button onClick={resetCount}>Reset</button>
    </div>
  );
}

// With custom store
function CounterWithCustomStore() {
  const customStore = createStore();
  const resetCount = useResetAtom(countAtom, { store: customStore });
  
  return <button onClick={resetCount}>Reset</button>;
}

Reducer Utilities

useReducerAtom Hook (Deprecated)

Hook for using reducer pattern with atoms.

/**
 * @deprecated Use atomWithReducer and useAtom instead
 * Hook for using reducer pattern with atoms
 * @param anAtom - PrimitiveAtom to apply reducer to
 * @param reducer - Reducer function
 * @param options - Optional configuration
 * @returns Tuple of [value, dispatch function]
 */
function useReducerAtom<Value, Action>(
  anAtom: PrimitiveAtom<Value>,
  reducer: (value: Value, action: Action) => Value,
  options?: Options
): [Value, (action: Action) => void];

Usage Examples:

import { useReducerAtom } from "jotai/utils";

const countAtom = atom(0);

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;
  }
};

function Counter() {
  // Note: This is deprecated, use atomWithReducer instead
  const [count, dispatch] = useReducerAtom(countAtom, countReducer);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "set", value: 0 })}>Reset</button>
    </div>
  );
}

Callback Utilities

useAtomCallback Hook

Hook for creating callbacks that can read and write atoms.

/**
 * Hook for creating callbacks that can read and write atoms
 * @param callback - Callback function with get and set access
 * @param options - Optional configuration
 * @returns Memoized callback function
 */
function useAtomCallback<Result, Args extends unknown[]>(
  callback: (get: Getter, set: Setter, ...args: Args) => Result,
  options?: Options
): (...args: Args) => Result;

/**
 * Hook for creating async callbacks that can read and write atoms
 * @param callback - Async callback function with get and set access
 * @param options - Optional configuration
 * @returns Memoized async callback function
 */
function useAtomCallback<Result, Args extends unknown[]>(
  callback: (get: Getter, set: Setter, ...args: Args) => Promise<Result>,
  options?: Options
): (...args: Args) => Promise<Result>;

type Getter = <Value>(atom: Atom<Value>) => Value;
type Setter = <Value, Args extends unknown[], Result>(
  atom: WritableAtom<Value, Args, Result>,
  ...args: Args
) => Result;

Usage Examples:

import { useAtomCallback } from "jotai/utils";

const countAtom = atom(0);
const userAtom = atom({ name: "Alice", score: 0 });

function GameComponent() {
  const incrementScore = useAtomCallback(
    (get, set) => {
      const currentCount = get(countAtom);
      const currentUser = get(userAtom);
      
      set(countAtom, currentCount + 1);
      set(userAtom, { 
        ...currentUser, 
        score: currentUser.score + currentCount 
      });
    }
  );

  const resetGame = useAtomCallback(
    (get, set) => {
      set(countAtom, 0);
      set(userAtom, (prev) => ({ ...prev, score: 0 }));
    }
  );

  // Callback with parameters
  const addPoints = useAtomCallback(
    (get, set, points: number) => {
      const currentUser = get(userAtom);
      set(userAtom, { 
        ...currentUser, 
        score: currentUser.score + points 
      });
    }
  );

  return (
    <div>
      <button onClick={incrementScore}>Increment Score</button>
      <button onClick={() => addPoints(10)}>Add 10 Points</button>
      <button onClick={resetGame}>Reset Game</button>
    </div>
  );
}

// Async callback example
function DataComponent() {
  const loadUserData = useAtomCallback(
    async (get, set, userId: string) => {
      try {
        const response = await fetch(`/api/users/${userId}`);
        const userData = await response.json();
        set(userAtom, userData);
        return userData;
      } catch (error) {
        console.error("Failed to load user data:", error);
        throw error;
      }
    }
  );

  const handleLoadUser = async () => {
    try {
      const user = await loadUserData("123");
      console.log("Loaded user:", user);
    } catch (error) {
      console.error("Error:", error);
    }
  };

  return (
    <button onClick={handleLoadUser}>Load User</button>
  );
}

Hydration Utilities

useHydrateAtoms Hook

Hook for SSR hydration of atom values, supporting various data structures.

/**
 * Hook for hydrating atoms with values (array form)
 * @param values - Array of [atom, value] tuples
 * @param options - Optional configuration
 */
function useHydrateAtoms<Values extends readonly (readonly [Atom<unknown>, unknown])[]>(
  values: Values,
  options?: Options
): void;

/**
 * Hook for hydrating atoms with values (Map form)
 * @param values - Map of atoms to values
 * @param options - Optional configuration
 */
function useHydrateAtoms<Values extends Map<Atom<unknown>, unknown>>(
  values: Values,
  options?: Options
): void;

/**
 * Hook for hydrating atoms with values (Iterable form)
 * @param values - Iterable of [atom, value] tuples
 * @param options - Optional configuration
 */
function useHydrateAtoms<Values extends Iterable<readonly [Atom<unknown>, unknown]>>(
  values: Values,
  options?: Options
): void;

Usage Examples:

import { useHydrateAtoms } from "jotai/utils";

const userAtom = atom({ name: "", email: "" });
const themeAtom = atom("light");
const countAtom = atom(0);

// Array form
function App({ initialUserData, initialTheme, initialCount }) {
  useHydrateAtoms([
    [userAtom, initialUserData],
    [themeAtom, initialTheme],
    [countAtom, initialCount]
  ]);

  return <MainContent />;
}

// Map form
function AppWithMap({ serverData }) {
  const hydrateMap = new Map([
    [userAtom, serverData.user],
    [themeAtom, serverData.theme],
    [countAtom, serverData.count]
  ]);

  useHydrateAtoms(hydrateMap);

  return <MainContent />;
}

// Custom store
function AppWithCustomStore({ serverData }) {
  const customStore = createStore();
  
  useHydrateAtoms([
    [userAtom, serverData.user],
    [themeAtom, serverData.theme]
  ], { store: customStore });

  return (
    <Provider store={customStore}>
      <MainContent />
    </Provider>
  );
}

// Next.js SSR example
function MyApp({ Component, pageProps }) {
  return (
    <Provider>
      <HydrateAtoms {...pageProps}>
        <Component {...pageProps} />
      </HydrateAtoms>
    </Provider>
  );
}

function HydrateAtoms({ children, ...props }) {
  useHydrateAtoms([
    [userAtom, props.user],
    [themeAtom, props.theme]
  ]);
  return children;
}

export async function getServerSideProps() {
  const user = await fetchUser();
  const theme = await fetchTheme();
  
  return {
    props: {
      user,
      theme
    }
  };
}

TypeScript Utilities

INTERNAL_InferAtomTuples Type

Internal type utility for inferring atom tuple types.

/**
 * Internal type for inferring atom tuple types
 * Used internally by useHydrateAtoms for type inference
 */
type INTERNAL_InferAtomTuples<T> = T extends readonly (readonly [infer A, infer V])[]
  ? A extends Atom<infer AV>
    ? readonly (readonly [A, AV])[]
    : never
  : never;

Advanced Usage Patterns

Combining Multiple Utilities

import { 
  atomWithReset, 
  useResetAtom, 
  useAtomCallback,
  useHydrateAtoms 
} from "jotai/utils";

const gameStateAtom = atomWithReset({
  score: 0,
  level: 1,
  lives: 3,
  isPlaying: false
});

function GameManager({ initialGameState }) {
  // Hydrate with server data
  useHydrateAtoms([[gameStateAtom, initialGameState]]);
  
  const resetGame = useResetAtom(gameStateAtom);
  
  const advanceLevel = useAtomCallback((get, set) => {
    const currentState = get(gameStateAtom);
    set(gameStateAtom, {
      ...currentState,
      level: currentState.level + 1,
      score: currentState.score + 1000
    });
  });

  const gameOver = useAtomCallback((get, set) => {
    const currentState = get(gameStateAtom);
    set(gameStateAtom, {
      ...currentState,
      isPlaying: false,
      lives: 0
    });
  });

  return (
    <div>
      <button onClick={resetGame}>New Game</button>
      <button onClick={advanceLevel}>Next Level</button>
      <button onClick={gameOver}>Game Over</button>
    </div>
  );
}

Custom Hook Patterns

import { useAtomCallback, useAtomValue } from "jotai/utils";

// Custom hook for managing shopping cart
function useShoppingCart() {
  const items = useAtomValue(cartItemsAtom);
  const totalPrice = useAtomValue(cartTotalAtom);

  const addItem = useAtomCallback((get, set, product: Product) => {
    const currentItems = get(cartItemsAtom);
    const existingItem = currentItems.find(item => item.id === product.id);
    
    if (existingItem) {
      set(cartItemsAtom, currentItems.map(item =>
        item.id === product.id 
          ? { ...item, quantity: item.quantity + 1 }
          : item
      ));
    } else {
      set(cartItemsAtom, [...currentItems, { ...product, quantity: 1 }]);
    }
  });

  const removeItem = useAtomCallback((get, set, productId: string) => {
    const currentItems = get(cartItemsAtom);
    set(cartItemsAtom, currentItems.filter(item => item.id !== productId));
  });

  const clearCart = useAtomCallback((get, set) => {
    set(cartItemsAtom, []);
  });

  return {
    items,
    totalPrice,
    addItem,
    removeItem,
    clearCart
  };
}

// Usage in component
function ShoppingCart() {
  const { items, totalPrice, addItem, removeItem, clearCart } = useShoppingCart();

  return (
    <div>
      <h2>Cart (${totalPrice})</h2>
      {items.map(item => (
        <div key={item.id}>
          {item.name} x {item.quantity}
          <button onClick={() => removeItem(item.id)}>Remove</button>
        </div>
      ))}
      <button onClick={clearCart}>Clear Cart</button>
    </div>
  );
}

SSR and Hydration Patterns

import { useHydrateAtoms } from "jotai/utils";

// Universal data loading pattern
function UniversalApp({ ssrData }) {
  // Hydrate atoms with SSR data
  useHydrateAtoms([
    [userAtom, ssrData.user],
    [settingsAtom, ssrData.settings],
    [notificationsAtom, ssrData.notifications]
  ]);

  return <AppContent />;
}

// Conditional hydration based on environment
function ConditionalHydration({ children, serverData }) {
  const hydrationData = useMemo(() => {
    if (typeof window === 'undefined') {
      // Server-side: no hydration needed
      return [];
    }
    
    // Client-side: hydrate with server data
    return [
      [userAtom, serverData.user],
      [sessionAtom, serverData.session]
    ];
  }, [serverData]);

  useHydrateAtoms(hydrationData);

  return children;
}

// Progressive hydration
function ProgressiveHydration({ criticalData, deferredData, children }) {
  // Immediate hydration for critical data
  useHydrateAtoms([
    [userAtom, criticalData.user],
    [authAtom, criticalData.auth]
  ]);

  // Deferred hydration for non-critical data
  useEffect(() => {
    const timer = setTimeout(() => {
      // Hydrate non-critical data after initial render
      if (deferredData) {
        // Manual store updates for deferred data
        const store = getDefaultStore();
        store.set(preferencesAtom, deferredData.preferences);
        store.set(analyticsAtom, deferredData.analytics);
      }
    }, 0);

    return () => clearTimeout(timer);
  }, [deferredData]);

  return children;
}

docs

core-atoms.md

index.md

react-integration.md

react-utilities.md

vanilla-utilities.md

tile.json