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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@okenneth/reactive-state

v1.0.1

Published

A lightweight, reactive state management library for React with TypeScript support

Readme

ReactiveState

A lightweight, reactive state management library for React with TypeScript support.

Features

  • Type Safety: Fully typed API using TypeScript.
  • Lightweight: Minimal bundle size, avoiding unnecessary dependencies.
  • Intuitive API: Simple, hook-based API that feels native to React.
  • Fine-Grained Reactivity: Components only re-render when the specific part of the state they depend on changes.
  • Flexible: Support for both atomic and global store patterns.
  • Optimized Performance: Efficient state updates, avoiding unnecessary renders.
  • Persistable State: Support for localStorage or custom storage solutions.
  • Asynchronous Actions: Built-in support for async operations.
  • Selectors & Derivations: Computed state and optimized subscriptions.
  • Middleware Support: Logging, async processing, validation, and more.
  • SSR Compatible: Works with server-side rendering frameworks like Next.js.

Installation

npm install @okenneth/reactive-state
# or
yarn add @okenneth/reactive-state
# or
pnpm add @okenneth/reactive-state

Basic Usage

Creating a Store

import { createStore } from '@okenneth/reactive-state';

// Define your state type
interface CounterState {
  count: number;
  incrementBy: number;
}

// Create a store with initial state
const counterStore = createStore<CounterState>({
  initialState: {
    count: 0,
    incrementBy: 1
  }
});

// Use the store directly
counterStore.setState(state => ({
  ...state,
  count: state.count + state.incrementBy
}));

console.log(counterStore.getState()); // { count: 1, incrementBy: 1 }

Using Stores in Components

import React from 'react';
import { useStore, useSelector } from '@okenneth/reactive-state';

function Counter() {
  // Subscribe to the entire store
  const state = useStore(counterStore);
  
  const increment = () => {
    counterStore.setState(state => ({
      ...state,
      count: state.count + state.incrementBy
    }), 'INCREMENT');
  };
  
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={increment}>Increment by {state.incrementBy}</button>
    </div>
  );
}

function CountDisplay() {
  // Subscribe to only the count part of the state
  const count = useSelector(
    counterStore,
    state => state.count
  );
  
  // This component only re-renders when count changes
  return <p>Current count: {count}</p>;
}

Using the Combined State and Setter Hook

import React from 'react';
import { useStoreState } from '@okenneth/reactive-state';

function CounterWithHook() {
  // Get both state and setState in one hook
  const [state, setState] = useStoreState(counterStore);
  
  const increment = () => {
    setState(state => ({
      ...state,
      count: state.count + state.incrementBy
    }), 'INCREMENT');
  };
  
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={increment}>Increment by {state.incrementBy}</button>
    </div>
  );
}

Async Actions

import { AsyncAction } from '@okenneth/reactive-state';

// Define an async action
const fetchUserData: AsyncAction<UserState> = async (
  state,
  setState,
  getState
) => {
  // Show loading state
  setState(state => ({ ...state, loading: true }), 'FETCH_USER_START');
  
  try {
    // Fetch data from API
    const response = await fetch('/api/user');
    const userData = await response.json();
    
    // Update state with the result
    setState(state => ({
      ...state,
      user: userData,
      loading: false
    }), 'FETCH_USER_SUCCESS');
    
    return userData;
  } catch (error) {
    // Handle error
    setState(state => ({
      ...state,
      error: error.message,
      loading: false
    }), 'FETCH_USER_ERROR');
    
    throw error;
  }
};

// Dispatch the action
userStore.dispatch(fetchUserData).then(user => {
  console.log('User data loaded:', user);
}).catch(error => {
  console.error('Failed to load user data:', error);
});

Creating Derived State

import { createDerivedStore } from '@okenneth/reactive-state';

// Create a derived store that depends on another store
const doubledCountStore = createDerivedStore(
  [counterStore],
  (counterState) => ({
    doubledCount: counterState.count * 2,
    isEven: (counterState.count * 2) % 2 === 0
  })
);

// Use in a component with useStore hook
function DoubledCounter() {
  const { doubledCount, isEven } = useStore(doubledCountStore);
  
  return (
    <div>
      <p>Doubled count: {doubledCount}</p>
      <p>Is even: {isEven ? 'Yes' : 'No'}</p>
    </div>
  );
}

Using Middleware

import {
  createStore,
  createLoggerMiddleware,
  createPerformanceMiddleware
} from '@okenneth/reactive-state';

// Create a store with middleware
const storeWithMiddleware = createStore({
  initialState: { count: 0 },
  middleware: [
    createLoggerMiddleware({ name: 'CounterStore' }),
    createPerformanceMiddleware({ warnIfExceeds: 5 })
  ]
});

Persistable State

import { createStore } from '@okenneth/reactive-state';

// Create a store with persistence enabled
const persistedStore = createStore({
  initialState: { preferences: { theme: 'light' } },
  persist: {
    key: 'app-preferences',
    storage: localStorage // Optional, defaults to localStorage
  }
});

Undo/Redo Functionality

import React from 'react';
import { createStore, createUndoRedoMiddleware } from '@okenneth/reactive-state';

const initialState = { text: '' };

// Create the undo/redo middleware
const { middleware, controls } = createUndoRedoMiddleware();

// Create a store with the middleware
const textStore = createStore({
  initialState,
  middleware: [middleware]
});

function TextEditor() {
  const [state, setState] = useStoreState(textStore);
  
  const handleChange = (e) => {
    setState({ text: e.target.value }, 'UPDATE_TEXT');
  };
  
  const handleUndo = () => {
    controls.undo(newState => textStore.setState(newState));
  };
  
  const handleRedo = () => {
    controls.redo(newState => textStore.setState(newState));
  };
  
  return (
    <div>
      <textarea
        value={state.text}
        onChange={handleChange}
      />
      <button
        onClick={handleUndo}
        disabled={!controls.canUndo()}
      >
        Undo
      </button>
      <button
        onClick={handleRedo}
        disabled={!controls.canRedo()}
      >
        Redo
      </button>
    </div>
  );
}

Advanced Usage

Creating a Store Hook Factory

import { createStoreHook } from '@okenneth/reactive-state';

// Create a store and a custom hook for it
const { store: todoStore, useStore: useTodoStore } = createStoreHook({
  initialState: {
    todos: [],
    filter: 'all'
  }
});

// Use in components
function TodoList() {
  const [state, setState] = useTodoStore();
  
  // Now you can use the state and setState directly
  // without having to import the store separately
}

Custom Middleware

import { Middleware } from '@okenneth/reactive-state';

// Create a custom middleware
const analyticsMiddleware: Middleware<any> = (nextState, prevState, action) => {
  if (action) {
    // Send action to analytics service
    sendToAnalytics({
      action,
      timestamp: new Date().toISOString()
    });
  }
  
  return nextState;
};

// Use the middleware
const storeWithAnalytics = createStore({
  initialState: { /* ... */ },
  middleware: [analyticsMiddleware]
});

SSR Support

ReactiveState works seamlessly with server-side rendering:

// On the server
const serverStore = createStore({ initialState: { /* ... */ } });

// Render with initial data
const html = renderToString(<App store={serverStore} />);

// Serialize state for client hydration
const initialData = serverStore.getState();

// On the client
const clientStore = createStore({ 
  initialState: window.__INITIAL_DATA__ || { /* fallback */ } 
});

// Hydrate the application
hydrate(<App store={clientStore} />, document.getElementById('root'));

Performance Optimization

ReactiveState is designed for optimal performance:

  1. Fine-grained reactivity: Components only re-render when their specific dependencies change.
  2. Shallow equality checks: Prevents unnecessary renders when state references change but values don't.
  3. Memoized selectors: The useSelector hook only triggers updates when the selected state changes.
  4. Batch updates: Multiple state changes within the same event loop are batched to avoid cascading renders.

API Reference

Core

createStore<T>(options: CreateStoreOptions<T>): Store<T>

Creates a new store with the provided options.

createDerivedStore<D, T>(dependencies, deriveFn): Store<T>

Creates a derived store that depends on other stores.

Hooks

useStore<T>(store: Store<T>): T

Subscribes to a store and returns its current state.

useSelector<T, R>(store: Store<T>, selector: (state: T) => R, equalityFn?): R

Subscribes to a specific part of a store's state.

useSetState<T>(store: Store<T>): (updater, action?) => void

Returns a memoized setState function for the store.

useStoreState<T>(store: Store<T>): [T, (updater, action?) => void]

Returns both the state and setState function.

useSelectorState<T, R>(store, selector, equalityFn?): [R, (updater, action?) => void]

Returns both the selected state and setState function.

Middleware

createLoggerMiddleware(options?): Middleware<T>

Creates middleware that logs state changes.

createPerformanceMiddleware(options?): Middleware<T>

Creates middleware that tracks update performance.

createThrottleMiddleware(options?): Middleware<T>

Creates middleware that throttles updates.

createValidationMiddleware(validator): Middleware<T>

Creates middleware that validates state changes.

createUndoRedoMiddleware(options?): { middleware, controls }

Creates middleware and controls for undo/redo functionality.

License

MIT