EveryState + React 18
A complete task manager built with @everystate/react. Every file shown below. No magic, no hidden code.
Install: npm i @everystate/core @everystate/react react react-dom
5 files, ~130 lines total
Store, business logic, 3 React components. That's the whole app.
Zero business logic in components
Components read (usePath) and publish (useIntent). Subscribers handle logic.
Testable without React
Every behavior is testable with store.set() + store.get(). No render needed.
import { createEveryState } from '@everystate/core';
// ---- Store (created outside React) ----
export const store = createEveryState({
state: {
tasks: [],
taskCount: 0,
filter: 'all', // 'all' | 'active' | 'completed'
},
derived: {
tasks: { filtered: [] },
},
});
// ---- Business logic (pure subscribers, no React imports) ----
let nextId = 1;
store.subscribe('intent.addTask', (text) => {
const t = String(text || '').trim();
if (!t) return;
const tasks = store.get('state.tasks') || [];
const next = [...tasks, { id: nextId++, text: t, completed: false }];
store.set('state.tasks', next);
store.set('state.taskCount', next.length);
});
store.subscribe('intent.toggleTask', (id) => {
const tasks = store.get('state.tasks') || [];
store.set('state.tasks',
tasks.map(t => t.id === id ? { ...t, completed: !t.completed } : t)
);
});
store.subscribe('intent.deleteTask', (id) => {
const tasks = (store.get('state.tasks') || []).filter(t => t.id !== id);
store.set('state.tasks', tasks);
store.set('state.taskCount', tasks.length);
});
store.subscribe('intent.changeFilter', (filter) => {
store.set('state.filter', filter);
});
// ---- Derived state ----
function recomputeFiltered() {
const tasks = store.get('state.tasks') || [];
const filter = store.get('state.filter');
const filtered = tasks.filter(t => {
if (filter === 'active') return !t.completed;
if (filter === 'completed') return t.completed;
return true;
});
store.set('derived.tasks.filtered', filtered);
}
store.subscribe('state.tasks', recomputeFiltered);
store.subscribe('state.filter', recomputeFiltered);
recomputeFiltered();
import { EventStateProvider } from '@everystate/react';
import { store } from './store.js';
import { TaskInput } from './TaskInput.jsx';
import { TaskList } from './TaskList.jsx';
import { FilterBar } from './FilterBar.jsx';
export default function App() {
return (
<EventStateProvider store={store}>
<div className="app">
<h1>Tasks</h1>
<TaskInput />
<FilterBar />
<TaskList />
</div>
</EventStateProvider>
);
}
import { useState } from 'react';
import { useIntent } from '@everystate/react';
export function TaskInput() {
const [text, setText] = useState('');
const addTask = useIntent('intent.addTask');
function submit() {
if (text.trim()) {
addTask(text);
setText('');
}
}
return (
<div className="task-input">
<input
value={text}
onChange={e => setText(e.target.value)}
placeholder="What needs to be done?"
onKeyDown={e => {
if (e.key === 'Enter') submit();
}}
/>
<button onClick={submit}>Add</button>
</div>
);
}
import { usePath, useIntent } from '@everystate/react';
export function FilterBar() {
const filter = usePath('state.filter');
const count = usePath('state.taskCount');
const setFilter = useIntent('intent.changeFilter');
return (
<div className="filter-bar">
<span>
{count} task{count === 1 ? '' : 's'}
</span>
<div className="filter-buttons">
{['all', 'active', 'completed'].map(f => (
<button
key={f}
className={filter === f ? 'active' : ''}
onClick={() => setFilter(f)}
>
{f}
</button>
))}
</div>
</div>
);
}
import { usePath, useIntent } from '@everystate/react';
export function TaskList() {
const items = usePath('derived.tasks.filtered');
const toggle = useIntent('intent.toggleTask');
const del = useIntent('intent.deleteTask');
if (!items || items.length === 0) {
return <p className="empty">No tasks match the current filter.</p>;
}
return (
<ul className="task-list">
{items.map(task => (
<li key={task.id} className={task.completed ? 'done' : ''}>
<input
type="checkbox"
checked={task.completed}
onChange={() => toggle(task.id)}
/>
<span className="task-text">{task.text}</span>
<button className="delete-btn" onClick={() => del(task.id)}>
×
</button>
</li>
))}
</ul>
);
}
// No React imports. No render(). No screen.getByRole(). Just state.
import { store } from './store.js';
test('addTask creates a task and increments count', () => {
store.set('intent.addTask', 'Buy milk');
expect(store.get('state.taskCount')).toBe(1);
expect(store.get('state.tasks')[0].text).toBe('Buy milk');
});
test('toggleTask flips completed', () => {
const id = store.get('state.tasks')[0].id;
store.set('intent.toggleTask', id);
expect(store.get('state.tasks')[0].completed).toBe(true);
});
test('filter shows only active tasks', () => {
store.set('intent.addTask', 'Walk dog');
store.set('intent.changeFilter', 'active');
const filtered = store.get('derived.tasks.filtered');
expect(filtered.every(t => !t.completed)).toBe(true);
});
test('deleteTask removes task and decrements count', () => {
const before = store.get('state.taskCount');
const id = store.get('state.tasks')[0].id;
store.set('intent.deleteTask', id);
expect(store.get('state.taskCount')).toBe(before - 1);
});
The Entire React Adapter (~50 lines)
This is the full source of @everystate/react. No hidden abstractions.
import { createContext, useContext, useMemo, useSyncExternalStore } from 'react';
const EventStateContext = createContext(null);
// -- Provider (dependency injection, not state management) --
export function EventStateProvider({ store, children }) {
return (
<EventStateContext.Provider value={store}>
{children}
</EventStateContext.Provider>
);
}
export function useStore() {
const store = useContext(EventStateContext);
if (!store) {
throw new Error(
'useStore: no store found. Wrap your tree in <EventStateProvider store={store}>.'
);
}
return store;
}
// -- Read a single path (concurrent-mode safe) --
export function usePath(path) {
const store = useStore();
const subscribe = useMemo(
() => (onStoreChange) => store.subscribe(path, () => onStoreChange()),
[store, path]
);
const getSnapshot = useMemo(
() => () => store.get(path),
[store, path]
);
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
}
// -- Publish to a path (stable, memoized) --
export function useIntent(path) {
const store = useStore();
return useMemo(
() => (value) => store.set(path, value),
[store, path]
);
}
// -- Wildcard subscription --
export function useWildcard(wildcardPath) {
const store = useStore();
const parentPath = wildcardPath.endsWith('.*')
? wildcardPath.slice(0, -2)
: wildcardPath;
const subscribe = useMemo(
() => (onStoreChange) => store.subscribe(wildcardPath, () => onStoreChange()),
[store, wildcardPath]
);
const getSnapshot = useMemo(
() => () => store.get(parentPath),
[store, parentPath]
);
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
}
// -- Async with status tracking --
export function useAsync(path) {
const store = useStore();
const data = usePath(`${path}.data`);
const status = usePath(`${path}.status`);
const error = usePath(`${path}.error`);
const execute = useMemo(
() => (fetcher) => store.setAsync(path, fetcher),
[store, path]
);
const cancel = useMemo(
() => () => store.cancel(path),
[store, path]
);
return { data, status, error, execute, cancel };
}
React vs Vue - Same Store, Different Adapter
The store.js file is identical. Only the components change.
import { usePath, useIntent } from '@everystate/react';
export function FilterBar() {
const filter = usePath('state.filter');
const setFilter = useIntent('intent.changeFilter');
return (
<div>
{['all', 'active', 'completed'].map(f => (
<button
key={f}
className={filter === f ? 'active' : ''}
onClick={() => setFilter(f)}
>
{f}
</button>
))}
</div>
);
}
<script setup>
import { usePath, useIntent } from '@everystate/vue';
const filter = usePath('state.filter');
const setFilter = useIntent('intent.changeFilter');
</script>
<template>
<div>
<button
v-for="f in ['all', 'active', 'completed']"
:key="f"
:class="{ active: filter === f }"
@click="setFilter(f)"
>
{{ f }}
</button>
</div>
</template>
Key Takeaways
Store lives outside React
Created once in store.js. Independent lifecycle. No useReducer, no createSlice.
Components are thin
usePath reads, useIntent writes. Components never compute or mutate state directly.
Logic is in subscribers
All business rules live in store.subscribe() handlers. Testable without rendering any component.
Concurrent-mode safe
usePath is built on React 18's useSyncExternalStore. No tearing, no stale reads.
Derived state is explicit
Filtered tasks are a subscriber that writes to derived.*. Components just read the result.
Same store works in Vue
The store.js file is identical across frameworks. Only the adapter imports change. See Vue version.