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.

store.js store + logic
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();
App.jsx root component
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>
  );
}
TaskInput.jsx publish intent
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>
  );
}
FilterBar.jsx read + publish
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>
  );
}
TaskList.jsx read derived + publish intents
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)}>
            &times;
          </button>
        </li>
      ))}
    </ul>
  );
}
store.test.js test without React
// 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.

@everystate/react/eventStateReact.js adapter source
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.

React: FilterBar.jsx
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>
  );
}
Vue: FilterBar.vue
<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.

Full React API Reference npm GitHub