DEV Community

Cover image for I Built a Tiny State Management Library in TypeScript atostate
Mahmoud shahin
Mahmoud shahin

Posted on

I Built a Tiny State Management Library in TypeScript atostate

State management is one of those topics every frontend developer eventually bumps into.

At first, everything is simple:

  • local state
  • props
  • maybe a few shared variables

Then the app grows…
and suddenly state is everywhere, duplicated, out of sync, and hard to reason about.

Big libraries solve this but often with boilerplate, mental overhead, or framework lock-in.

So I decided to build my own minimal solution.

Meet atostate 👋


What is atostate?

atostate is a tiny tool state management library for JavaScript and TypeScript.

Its goal is very simple:

Provide a predictable global store with subscriptions and strong typing without complexity.

No framework assumptions.
No magic.
Just state + rules.


Why I built it

I wanted a library that:

  • Is small and readable
  • Works in vanilla JS or TS
  • Doesn’t force Redux-style architecture
  • Is easy to debug and reason about

Creating a store

import { createStore } from 'atostate';

type State = {
  count: number;
};

const store = createStore<State>({
  count: 0,
});
Enter fullscreen mode Exit fullscreen mode

That’s it.
You now have a global store.


Updating state

State updates are explicit and predictable:

store.setState({ count: 1 });

store.setState((prev) => ({
  ...prev,
  count: prev.count + 1,
}));
Enter fullscreen mode Exit fullscreen mode

No direct mutation.
No hidden side effects.


Subscribing to changes

Subscribe to all state changes:

store.subscribe(() => {
  console.log('State changed:', store.getState());
});
Enter fullscreen mode Exit fullscreen mode

Or subscribe to only what you care about:

store.subscribe(
  (state) => state.count,
  (count, prev) => {
    console.log('Count changed:', prev, '', count);
  }
);
Enter fullscreen mode Exit fullscreen mode

This makes updates efficient and intentional.


Selectors + equality checks

Subscriptions only re-run when their selected slice actually changes.

import { shallowEqual } from 'atostate';

store.subscribe(
  (state) => state.user,
  (user) => {
    console.log('User changed:', user);
  },
  shallowEqual
);
Enter fullscreen mode Exit fullscreen mode

No unnecessary re-runs.
No wasted work.


Actions: organizing logic cleanly

To keep logic out of UI code, atostate provides a lightweight actions API.

const counter = store.actions(({ setState }) => ({
  increment() {
    setState((s) => ({ ...s, count: s.count + 1 }));
  },
  reset() {
    setState({ count: 0 });
  },
}));

counter.increment();
counter.reset();
Enter fullscreen mode Exit fullscreen mode

Simple, explicit, and easy to test.


Optional Redux-style reducers

If you like reducers and dispatch, atostate supports that too optionally.

type Action =
  | { type: 'inc' }
  | { type: 'set'; value: number };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'inc':
      return { ...state, count: state.count + 1 };
    case 'set':
      return { ...state, count: action.value };
    default:
      return state;
  }
}

const store = createStore<State, Action>(
  { count: 0 },
  { reducer }
);

store.dispatch({ type: 'inc' });
Enter fullscreen mode Exit fullscreen mode

Use it only if you want it.


Middleware support

Cross-cutting concerns are handled via middleware.

Logger

import { loggerMiddleware } from 'atostate';

createStore(
  { count: 0 },
  { middleware: [loggerMiddleware('app')] }
);
Enter fullscreen mode Exit fullscreen mode

Persistence

import { persistMiddleware } from 'atostate';

createStore(
  { count: 0 },
  { middleware: [persistMiddleware('app-state')] }
);
Enter fullscreen mode Exit fullscreen mode

TypeScript-first

atostate is written entirely in TypeScript:

  • Typed state
  • Typed actions
  • Typed selectors
  • Great autocomplete

No any. No guessing.


What atostate is not

atostate intentionally avoids:

  • Framework-specific APIs
  • Heavy abstractions
  • Hidden reactivity
  • Large surface area

It’s a core state engine, not a full framework.


When should you use it?

atostate is a good fit if you want:

  • A small global store
  • Shared state across components
  • Strong typing
  • Minimal learning curve
  • Full control over architecture

If you enjoy understanding how your tools work and don’t want unnecessary complexity you might enjoy using it.

Feedback, ideas, and PRs are very welcome 🙌

Top comments (1)

Collapse
 
ashishsimplecoder profile image
Ashish Prajapati

Honestly, I like the APIs and the way you have used Direct Store methodology for managing the store.