Introduction
SynState is a lightweight, glitch-free state management library for TypeScript/JavaScript. Simple global state that scales to complex async pipelines — with built-in debounce, switchMap, and more. Fully compatible with React Compiler.
“SynState” is named after “Synchronized + State.” Derived values are always kept in sync through glitch-free propagation — no inconsistent intermediate states. (Details: How SynState Solved the Glitch)
Features
Section titled “Features”- 🎯 Simple State Management: Easy-to-use
createStateandcreateReducersimilar to ReactuseState/useReducer, but for global state. - 🔧 Built-in Async Operators:
debounce,throttle,switchMap, and more — handle complex async flows in a unified, declarative API without external libraries. - ⚡ Glitch-Free Through Every Operator: Every update is one depth-ordered pass over what it reaches, the ones
debounceorswitchMapstart later included, so no subscriber sees a half-updated graph, and an update costs in proportion to the nodes it reaches rather than the paths between them. Operators on Signals shows what an operator written as an effect loses. - 🏎️ Very Fast: In the benchmark summary, SynState stays within 2.7× of the fastest library in every scenario: the only library measured in all of them whose worst case stays in single digits. See each library’s worst case.
- 🚀 Lightweight: ~7.7 kB min+gzip with two external runtime dependencies (ts-data-forge and ts-std-forge, and the first depends on the second). Compare it with other libraries.
- ⚛️ React-Optimized: Fully compatible with React Compiler.
synstate-react-hookslets you add global state to any component in just a few lines. - 🌐 Framework Agnostic: Core library works as-is with vanilla JavaScript, Vue, Svelte, or any framework. For React and Preact, hooks wrappers (
synstate-react-hooks/synstate-preact-hooks) are available. For Preact, a Preact Signals integration (synstate-preact-signals) is also provided for fine-grained DOM updates without component re-renders. Without any framework, synstate-dom binds Observables directly to the DOM.
Quick Example
Section titled “Quick Example”With a single call to createState, you can add global state to your application:
import { createState } from 'synstate';
// Create a reactive stateconst [state, setState] = createState(0);
// Subscribe to changesstate.subscribe((count) => { console.log(count); // 0, 1});
// Update statesetState(1);createState creates a reactive state (InitializedObservable) and a setter function. Subscribers are called immediately with the initial value, and again whenever the state is updated.
createState also returns a third element with additional utilities:
const [ state, setState, { updateState, resetState, getSnapshot, initialState },] = createState(0);
updateState((prev) => prev + 2); // update using a function of the previous valuegetSnapshot(); // read current value synchronously (2)initialState; // the initial value passed to createState (0)
resetState(); // reset to initial value (0)getSnapshot(); // read current value synchronously (0)Deriving Values with pipe
Section titled “Deriving Values with pipe”SynState’s power comes from composing Observables. Use pipe to derive new values and derive to compute one value from several sources:
import { createState, derive, type InitializedObservable, map } from 'synstate';
const [count, setCount] = createState<number>(0);
// Read the current valueconsole.log(count.getSnapshot().value); // 0
// Derive new Observables using pipeconst doubled: InitializedObservable<number> = count.pipe(map((n) => n * 2));
// Derive one value from several Observablesconst combined: InitializedObservable<string> = derive( [count, doubled], (c, d) => `(${c}, ${d})`,);
// Subscribe to changescount.subscribe((value) => { console.log('count:', value); // 0, 1, 2, 3});
doubled.subscribe((value) => { console.log('doubled:', value); // 0, 2, 4, 6});
combined.subscribe((value) => { console.log(value); // "(0, 0)", "(1, 2)", "(2, 4)", "(3, 6)"});
// Update statesetCount(1);setCount(2);setCount(3);For a deeper understanding of how this declarative model works — and why it is better than managing state imperatively — see Declarative State Management.
Using with React
Section titled “Using with React”createState must be called at module scope (outside of components). SynState provides synstate-react-hooks, a companion package that bridges Observables and React:
import type * as React from 'react';import { createState } from 'synstate-react-hooks';
const [useUserState, setUserState] = createState({ name: '', email: '',});
const UserProfile = (): React.JSX.Element => { const user = useUserState();
return ( <div> <p>{`Name: ${user.name}`}</p> <button onClick={() => { setUserState({ name: 'Alice', email: 'alice@example.com', }); }} > {'Set User'} </button> </div> );};For more details, see React Integration.
Next Steps
Section titled “Next Steps”- Installation — Install SynState and optional companion packages.
- Why SynState? — Design philosophy, glitch-free guarantees, and use cases.
- Declarative State Management — Understand the reactive programming model with concrete examples.
- React Integration — Use SynState with React.