Primitive and flexible state management for React applications
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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.
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>;
}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>
);
}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>
);
}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
}
};
}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;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>
);
}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>
);
}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;
}