ワンクリックで
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.