一键导入
atom-state
Implement reactive state management with Effect Atom for React applications
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Implement reactive state management with Effect Atom for React applications
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Decide between Context Tag witness and capability patterns for dependency injection, understanding coupling trade-offs
Generate comprehensive predicates and orders for domain types using typeclass patterns
Design and compose Effect layers for clean dependency management
Implement Effect services as fine-grained capabilities avoiding monolithic designs
Implement typeclasses with curried signatures and dual APIs for both data-first and data-last usage
| name | atom-state |
| description | Implement reactive state management with Effect Atom for React applications |
Effect Atom is a reactive state management library for Effect that seamlessly integrates with React.
Atoms work by reference - they are stable containers for reactive state:
import * as Atom from "@effect-atom/atom-react"
// Atoms are created once and referenced throughout the app
export const counterAtom = Atom.make(0)
// Multiple components can reference the same atom
// All update when the atom value changes
Atoms automatically reset when no subscribers remain (unless marked with keepAlive):
// Resets when last subscriber unmounts
export const temporaryState = Atom.make(initialValue)
// Persists across component lifecycles
export const persistentState = Atom.make(initialValue).pipe(Atom.keepAlive)
Atom values are computed on-demand when subscribers access them.
import * as Atom from "@effect-atom/atom-react"
// Simple atom
export const count = Atom.make(0)
// Atom with object state
export interface CartState {
readonly items: ReadonlyArray<Item>
readonly total: number
}
export const cart = Atom.make<CartState>({
items: [],
total: 0
})
Use Atom.map or computed atoms with the get parameter:
// Derived via map
export const itemCount = Atom.map(cart, (c) => c.items.length)
export const isEmpty = Atom.map(cart, (c) => c.items.length === 0)
// Computed atom accessing other atoms
export const cartSummary = Atom.make((get) => {
const cartData = get(cart)
const count = get(itemCount)
return {
itemCount: count,
total: cartData.total,
isEmpty: count === 0
}
})
Use Atom.family for stable references to dynamically created atoms:
// Create atoms per entity ID
export const userAtoms = Atom.family((userId: string) =>
Atom.make<User | null>(null).pipe(Atom.keepAlive)
)
// Usage - always returns the same atom for a given ID
const userAtom = userAtoms(userId)
Use Atom.fn for operations with side effects:
import { Effect } from "effect"
// Function atom for side effects
export const addItem = Atom.fn(
Effect.fnUntraced(function* (item: Item) {
const current = yield* Atom.get(cart)
yield* Atom.set(cart, {
items: [...current.items, item],
total: current.total + item.price
})
})
)
// Clear cart operation
export const clearCart = Atom.fn(
Effect.fnUntraced(function* () {
yield* Atom.set(cart, { items: [], total: 0 })
})
)
Wrap Effect layers/services for use in atoms:
import { Layer } from "effect"
// Create runtime with services
export const runtime = Atom.runtime(
Layer.mergeAll(
DatabaseService.Live,
LoggerService.Live,
ApiClient.Live
)
)
// Use services in function atoms
export const fetchUserData = runtime.fn(
Effect.fnUntraced(function* (userId: string) {
const db = yield* DatabaseService
const user = yield* db.getUser(userId)
yield* Atom.set(userAtoms(userId), user)
return user
})
)
Configure global layers once at app initialization:
// App setup
Atom.runtime.addGlobalLayer(
Layer.mergeAll(
Logger.Live,
Tracer.Live,
Config.Live
)
)
Atoms can return Result types for explicit error handling:
import * as Result from "@effect-atom/atom/Result"
export const userData = Atom.make<Result.Result<User, Error>>(
Result.initial
)
// In component
const result = useAtomValue(userData)
Result.match(result, {
Initial: () => <Loading />,
Failure: (error) => <Error message={error.message} />,
Success: (user) => <UserProfile user={user} />
})
Convert streams into atoms that capture the latest value:
import { Stream } from "effect"
// Infinite stream becomes reactive atom
export const notifications = Atom.make(
Stream.fromEventListener(window, "notification").pipe(
Stream.map(parseNotification),
Stream.filter(isValid),
Stream.scan([], (acc, n) => [...acc, n].slice(-10))
)
)
Use Atom.pull for stream-based pagination:
export const pagedItems = Atom.pull(
Stream.fromIterable(itemsSource).pipe(
Stream.grouped(10) // Pages of 10 items
)
)
// In component - automatically fetches next page when called
const loadMore = useAtomSet(pagedItems)
Use Atom.kvs for persisted state:
import { BrowserKeyValueStore } from "@effect/platform-browser"
import * as Schema from "effect/Schema"
export const userSettings = Atom.kvs({
runtime: Atom.runtime(BrowserKeyValueStore.layerLocalStorage),
key: "user-settings",
schema: Schema.Struct({
theme: Schema.Literal("light", "dark"),
notifications: Schema.Boolean,
language: Schema.String
}),
defaultValue: () => ({
theme: "light",
notifications: true,
language: "en"
})
})
import { useAtomValue, useAtomSet, useAtom, useAtomSetPromise } from "@effect-atom/atom-react"
export function CartView() {
// Read only
const cartData = useAtomValue(cart)
const isEmpty = useAtomValue(isEmpty)
// Write only
const addItem = useAtomSet(addItem)
const clearCart = useAtomSet(clearCart)
// Both read and write
const [count, setCount] = useAtom(counterAtom)
// For async function atoms
const fetchData = useAtomSetPromise(fetchUserData)
return (
<div>
<div>Items: {cartData.items.length}</div>
<button onClick={() => addItem(newItem)}>Add</button>
<button onClick={() => clearCart()}>Clear</button>
</div>
)
}
Different components can read/write the same atom reactively:
// Component A - reads state
function CartDisplay() {
const cart = useAtomValue(cart)
return <div>Items: {cart.items.length}</div>
}
// Component B - modifies state
function CartActions() {
const addItem = useAtomSet(addItem)
return <button onClick={() => addItem(item)}>Add</button>
}
// Both update reactively when atom changes
Atoms support scoped effects with automatic cleanup:
export const wsConnection = Atom.make(
Effect.gen(function* () {
// Acquire resource
const ws = yield* Effect.acquireRelease(
connectWebSocket(),
(ws) => Effect.sync(() => ws.close())
)
return ws
})
)
// Finalizer runs when atom rebuilds or becomes unused
Atom.family for dynamically generated atom setskeepAlive)export const userDataAtom = Atom.make<Result.Result<User, Error>>(
Result.initial
)
export const loadUser = runtime.fn(
Effect.fnUntraced(function* (id: string) {
yield* Atom.set(userDataAtom, Result.initial)
const result = yield* Effect.either(
userService.fetchUser(id)
)
yield* Atom.set(
userDataAtom,
result._tag === "Right"
? Result.success(result.right)
: Result.failure(result.left)
)
})
)
export const updateItem = runtime.fn(
Effect.fnUntraced(function* (id: string, updates: Partial<Item>) {
const current = yield* Atom.get(itemsAtom)
// Optimistic update
yield* Atom.set(
itemsAtom,
current.map(item => item.id === id ? { ...item, ...updates } : item)
)
// Persist to server
const result = yield* Effect.either(api.updateItem(id, updates))
// Revert on failure
if (result._tag === "Left") {
yield* Atom.set(itemsAtom, current)
}
})
)
// Filter atom accessing other atoms
export const filteredItems = Atom.make((get) => {
const items = get(itemsAtom)
const searchTerm = get(searchAtom)
const activeFilters = get(filtersAtom)
return items.filter(item =>
item.name.includes(searchTerm) &&
activeFilters.every(f => f.predicate(item))
)
})
Effect Atom bridges Effect's powerful type system with React's rendering model, providing type-safe reactive state management with automatic cleanup and seamless Effect integration.