npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

zustand-lite

v0.7.3

Published

Zustand Lite builds upon zustand, by auto-generating selectors and simplifying API even more.

Downloads

236

Readme

Zustand Lite Image

🧠 Zustand Lite

npm version bundle size license Types GitHub stars

Zustand Lite is a zero-boilerplate state management built specifically for frontend developers who want powerful and scalable global state without the usual complexity. Designed for simplicity, it gives you everything you need out-of-the-box — from selectors to setters to middleware — while remaining lightweight and extensible. With seamless support for plugins, devtools, and state encapsulation, managing state becomes a breeze, not a chore.

A zero-boilerplate wrapper around Zustand, focused on ergonomics, plugins, and dynamic extension — inspired by zustand-x and getters/setters auto-generation patterns.

🛠️ Why zustand‑lite?

✅ In short

Zustand Lite delivers a simple, performant, and predictable way to manage UI state, letting your code stay focused on business logic, not infrastructure. Try it today and see how effortless frontend state can be!

While tools like React‑Query handle server state, UI state often gets bogged down by excessive boilerplate, tangled data flows, and hard‑to‑control side effects. Zustand Lite cuts through the noise.

✨ Why it matters

Boilerplate is the killer of productivity and obscures your real goal: business logic, which is usually far simpler than it appears.
Thanks to zustand‑lite, you can move faster without sacrificing clarity or flexibility.

  1. No context providers or React-specific setup.
  2. No mocking during tests
  3. No bloated dependency arrays
  4. Type-safe, simple API - simple usage with IDE support

🚀 Features

  • ⚛️ Minimal & Typed: Built on top of lightweight Zustand core, fully typed with TypeScript.
  • 🔄 Clean Separation: State and operations are well separated to minimize confusion.
  • 🚀 Blazing Performance: Selective updates and lean subscriptions keep things snappy.
  • 🪄 Zero Boilerplate: Does not require writing any idle code.
  • 🧪 Test Friendly: Easy to test, no additional hacks or mocks required.
  • 🔌 Shareable plugins: Plug custom logic directly into your store for extended capabilities.
  • 🧩 Optional middlewares: Seamlessly add devtools and persist middleware layers.
  • 🌱 Chainable API: create the store in a few simple composable steps.
  • 👁 Redux devtools labeling: Built-in clear, traceable action labeling useful for debugging.

🛠 Common Recipes

Simple store

import { createStore } from 'zustand-lite'

export const store = createStore({ foo: '' })

// Subscribe for your data changes.
function Component() {
    const foo = store.use.foo()
}

// Synchronous state accessor.
function onClick() {
    console.log(store.get().foo)
}

// Setting value with auto-generated setter.
function onClick() {
    store.set.foo('new-value')
}

Custom Setters

const store = createStore({ count: 0 })
    .extendSetters(({ get, set }) => ({
           increment: () => set.count(get().count + 1)
      }))

function Counter() {
    const count = store.use.count()
    return (
        <button onClick={store.set.increment}>
            Count: {count}
        </button>
    )
}

Advanced store

const initialState: {
    point: { x: number; y: number }
    rectangle: { a: number; b: number }
} = { 
    point: { x: 0, y: 0 }, 
    rectangle: { a: 20, b: 10 } 
}

export const store = createStore(initialState)
    .extendGetters(({ get }) => ({
        area: () => get().rectangle.x * get().rectangle.y,
    }))
    .extendSetters(({ get, set }) => ({
        translateX: (dx: number) =>
            set.bar({ x: get().point.x + dx, y: get().point.y }),
    }))
    .restrictState(['rectangle'])
// ^ Seal the store, so that certain fields are unavailable for 
// the outside context, while still being available for getters and setters.

// Subscribe for computed data changes. This new selector is auto-generated.
function Component() {
    const area = store.use.area()
}

// Make private value inaccessible.
function onClick() {
    console.log(store.get().rectangle)
    // ^ TS error, no value. It is not accessible 
    // anymore from outside the store.
}

// Call custom action.
function onClick() {
    store.set.translateX(7)
}

// Access native Zustand api (expect getState, setState, 
// which are available thrugh store.get() and store.set() 
// for simplicity and additional expresivness).
function Component() {
    const state = store.api.getInitialState()
}

Deep value getters

const initialState: { my: { foo: { bar: string} } } = {
    my: { foo: { bar: 'value' } },
}

export const store = createStore(initialState)
    .myFooBar(({ get }) => ({
        // Entire state is accessible with store.get()
        return get().my.foo.bar;
    }))
    .restrictState()

// Component will update only if deeply nested value will update.
function Component() {
    const myFooBar = store.use.myFooBar()
}

Automatic deep selectors

const initialState: { my: { foo: { bar: string } } } = {
    my: { foo: { bar: 'value' } },
}

export const store = createStore(initialState)

// Component will update only if deeply nested value will update. 
// Those selectors will be generated only for required attributes.
function Component() {
    const myFooBar = store.use.my.foo.bar()
}

Ad-hoc selectors

const initialState: { my: { foo: { bar: string } } } = {
    my: { foo: { bar: 'value' } },
}

export const store = createStore(initialState)

// If no auto-generated selector is available, 
// custom one may still be used.
function Component() {
    const myFooBar = store.use((state) => state.my.foo, customEquality)
}

Multi selectors

const initialState = {
    a: 'a', b: 'b', c: 'c', d: 'd',
}

export const store = createStore(initialState)

// Listen to multiple properties of the store at the same time
// for maximal brevity.
function Component() {
    const { a, c } = store.use(['a', 'c'])
}

Setting whole state

const initialState: { my: { foo: { bar: string } } } = {
    my: { foo: { bar: 'value' } },
}

export const store = createStore(initialState)

// State can be set with first level auto-generated 
// setters or with store.set
store.set((state) => ({ ...state, newField: 'newField' }))
// By default state is shallowly merged.
store.set({ newField: 'newField' })
// Emptying the state with replace: true flag.
store.set({}, true)

Overriding getters and setters

const initialState: {
    point: { x: number; y: number }
    rectangle: { a: number; b: number }
} = { 
    point: { x: 0, y: 0 }, 
    rectangle: { a: 20, b: 10 } 
}

export const store = createStore(initialState)
    .extendGetters(({ get }) => ({
        // get().point refers to the store value
        myPoint: () => transformToDifferentCoordinates(get().point),
    }))
    .extendGetters(({ get }) => ({
        // get.myPoint() will refer to the already transformed point 
        // from the previous getter. It will override the previous 
        // one, but can still accesess anything defined before.
        myPoint: () => soSomethingWithTransformedPoint(get.myPoint()),
    }))
    .restrictState()

Custom equality

const initialState: { rectangle: { a: number; b: number } } = {
    rectangle: { a: 20, b: 10 },
}

export const store = createStore(initialState)

// By default shallow equality is being used.
function Component() {
    const rectangle = store.use.rectangle(customEqualityFn)
}

Extending state

const initialState: { rectangle: { a: number; b: number } } = {
    rectangle: { a: 20, b: 10 },
}

export const store = createStore(initialState)
    .extendByState({ h: 30 })
    .extendGetters(({ get }) => ({ 
        volume() {
            return get().rectangle.a * get().rectange.b * get().h
        }
    }))

// By default shallow equality is being used.
function Component() {
    store.set.h(50)
    const rectangle = store.use.volume()
}

Testing features

function Component() {
    const dependencyA = store.use.dependencyA()
}

// No need to mock the store or add additional providers, just 
// interact with it in the usual way. Wrapping setter with act 
// might be needed to sync react updates.
test('Testing Component', () => ({
    render(<Component />)
    act(() => store.set.dependencyA(someValue))
    expect(storeDependantText).toBe(someValue)
})

🧠 API Overview

createStore(initialState, options)

Creates a typed store with your state and optional middleware.

Options:

| Key | Type | Description | | ------------- | -------------------------------------------------------------------------- | ------------------------------------ | | name | string | Name shown in Redux DevTools | | middlewares | { devtools?: true or DevtoolsOptions, persist?: true or PersistOptions } | Middleware configuration |

Chainable Methods

  • .extendByState(fn | object)
    Add additional state that can be reused later.
  • .extendGetters(fn)
    Add additional derived getters based on current state.
  • .extendSetters(fn)
    Add additional typed setters.
  • .composePlugin(plugin)
    Composes functionality of existing plugin ito your own store.
  • .restrictState(keys?: string[])
    Hide selected fields from the public API, returning a minimal store (removes config methods as well).

Store Interface

After creation, your store includes:

| Property | Purpose | | --------------------- | --------------------------------------------------------------- | | store.use.foo() | React hook for subscribing to foo | | store.use(selector) | React hook for subscribing to custom selector result | | store.get() | Direct synchronous access to whole state | | store.set.foo(v) | Set a new value for foo | | store.set(state) | Set an entire new state | | store.api | The native Zustand store API (getInitialState, subscribe, etc.) |


🧩 Plugin System

You can define plugins that inject additional state or behavior:

import { definePlugin } from 'zustand-lite'

export const withMyPlugin = definePlugin((store) =>
    // If plugin defines data, that and only that data is available inside
    // setters and getters.
    store
        .extendByState({ side: 1 })
        .extendGetters(({ get }) => ({
            // Every piece od data, getter or setter will be available in the custom
            // extendGetter and extendSetter, allowing for even more interactions.
            area() {
                return get().side * get().side
            },
        }))
        .extendSetters(({ set }) => ({
            area(area: number) {
                return set.side(Math.sqrt(area))
            },
        }))
)

Apply newly created plugin like this:

const store = createStore({}).composePlugin(withMyPlugin)

Any plugin state, getters and setters will be available for usage inside your own store.

🧪 Middlewares Integration

You can enable the most useful middlewares:

{
    name: 'MyApp/CounterStore',
    middlewares: {
        devtools: true,
        persist: {
            ...options,
        },
    }
}

🛠 Planned improvements

  • Configurable level of auto-generation. While I advise to keep store as flat as possible, good structured data is important. For deeper properties it might be more convenient to auto generate getters and setters for deeply nested properties as well. (partially done with hooks, entire state is selected for get from version 3.0.0, setters still generated for level one only)
  • Ability to specify equality function for extended getters. It's possible now, but requires to import hook from 'zustand' package, which is suboptimal (available from version 3.0.0 with use() function or deep auto-generated selectors. Still no possible for custom getters).
  • Implement subscribe with selector middleware

🧱 Built With

📘 License

MIT — free to use, extend, and improve.

🤝 Contributing

Pull requests, feedback, and ideas are welcome!
If you'd like to publish your own plugins, we recommend namespacing them under:

zustand-lite/plugin-*

or adding them to the main repository under the plugins directory.