Skip to content

zmandel/tinyreactive

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tinyReactive

A simple, efficient, and robust reactive data store to help write decoupled code.

It doubles as a learning resource through the provided samples you can run from the GitHub demos or your local clone.

A typical case is in frontend development between UI and data dependencies: UI components describe what slice of data they care about, the store notifies them when that slice changes, keeping the logic encapsulated on each component.

Highlights

  • Updates only the UI affected by a state change.
  • No virtual DOM or browser dependency, can be used too for UI-less scenarios.
  • Allows UI subscribers to use existing DOM nodes instead of recreating the component HTML.
  • Batches consecutive updates on the next frame draw or microtask.
  • Subscribers always receive a settled state.
  • Works via import or <script>.
  • No runtime dependencies.
  • Core store logic is about 100 lines of code (≈700 B minified and gzipped).

It's meant to stay small, for simple and medium-complexity scenarios. For larger-scale frameworks with similar store concepts and more features see Solid, Preact signals, Vue etc.

Used in production by tutorforme.org.

Fun fact: I first implemented this pattern in 1997 in C++ for "Microsoft Money" reactive UI updates from database changes. It was the first Microsoft program using reactive UI!

Table of contents

Basics

The store implementation lives in src/store.js with an ES module wrapper in src/store.module.js. The public API is:

Including src/store.js with a plain <script> tag makes the API available as StoreLib.createStore(...). In module-aware environments use import { createStore } from './src/store.module.js';.

const store = createStore(initialState); //returns an object with `get`, `set`, `patch`, and `subscribe`.

store.get(); //returns the latest snapshot of state.
store.set(newState); //replaces the state and queues notifications.
store.patch(partialState); //shallow merges state and queues notifications.
store.subscribe(callback, selector?); //registers a listener. The callback runs immediately with the initial selected value and only runs again when that value changes.

Internally, tinyReactive keeps a Set of subscribers. Each subscriber caches the last value from its selector, so updates fire only when needed. Notifications are deferred with requestAnimationFrame (or queueMicrotask/setTimeout outside the browser) to ensure all mutations settle before DOM work. Selector or subscriber failures are caught, logged, and unsubscribed so a single bug cannot stall the store or provide an inconsistent state.

Getting started

You can inspect and debug samples directly from the demos in GitHub Pages, or clone the repo:

git clone https://github.com/zmandel/tinyreactive.git
cd tinyreactive

The project is framework-free; open the minimal demo from your file explorer at samples/minimal/index.html directly in your browser (file:// protocol) For richer examples such as samples/tasks-app, run a local dev server (import modules need http).

cd samples/tasks-app
npm install
npm run dev

Usage

Import the store library (import or a <script>) and wire it to your UI code:

<div id="count"></div>
<button>Increment</button>

<script type="module">
  import { createStore } from './src/store.module.js';

  // Create a store with a primitive initial value.
  // Note: for complex states, use objects such as { count: 0, otherProp: true }.
  const store = createStore(0);

  // Subscribe to the entire store, since its just one value.
  // Note: for complex states, use selectors to subscribe to slices: state => state.count (see tasks-app sample)
  store.subscribe(value => {
    document.querySelector('#count').textContent = value;
  });

  document.querySelector('button').addEventListener('click', () => {
    store.set(store.get() + 1);
  });
</script>

In this case, the state is just a number primitive. When its an object, it detects changes with an internal shallow object comparison of the selector slice, using the included helper valuesEqual(a,b).

Samples

These samples can be run and debugged directly from the demos below.

  • samples/minimal wires a counter to UI. The subscription renders the count, and the click handler only sets the new count.
  • samples/tasks-app View store events from a notification panel (👁️) as state changes propagate to the UI. Independent subscriptions render the lists, summary, filter buttons, and notification panel. Selectors such as state => state.todos keep updates targeted to only what changed.

Tasks demo preview

Demos

Inspect the running examples directly:

tinyReactive on GitHub Pages

Open the devtools, set breakpoints, and watch how state changes travel through selectors into the UI.

Contributing

Issues and pull requests are welcome.

License

This project is licensed under the MIT License.

About

micro library for object reactivity. Subscribe to object changes.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •