Skip to content

Latest commit

 

History

History
318 lines (255 loc) · 7.43 KB

README.md

File metadata and controls

318 lines (255 loc) · 7.43 KB

use-one: A simple state management lib for React.js

Test and Release npm version license

Introduction

use-one is a lightweight state management library for React.js.

Features

  • Simple hook-based API with no complex concepts
  • Easy state sharing across components
  • Built-in persistence capabilities for stores and hook states
  • Minimal size (gzip ~2KB)
  • Written in TypeScript with full type safety

Table of Contents

Installation

npm

npm install use-one --save

pnpm

pnpm install use-one

Usage

Basic Example

// stores/count.ts
import { create } from 'use-one';

const initialState = { count: 0 };
const [use, store] = create(initialState);

const actions = {
  use,
  get state() {
    return store.getState();
  },
  increment() {
    store.setState({ count: this.state.count + 1 });
  },
  decrement() {
    store.setState({ count: this.state.count - 1 });
  },
};

export const countStore = Object.assign(actions, store);

Using the Store

// CountExample.tsx
import * as React from 'react';
import { countStore } from './stores/count';

const Counter = () => {
  const [state] = countStore.use();
  const { count } = state;

  return (
    <div>
      <button onClick={countStore.increment}>+1</button>
      <span>{count}</span>
      <button onClick={countStore.decrement}>-1</button>
      <button
        onClick={() => {
          setTimeout(() => {
            countStore.setState({
              count: countStore.state.count + 2,
            });
          }, 2000);
        }}
      >
        async +2
      </button>
    </div>
  );
};

const ShowCount = () => {
  const [state] = countStore.use();
  return <span>Count: {state.count}</span>;
};

export default function App() {
  return (
    <>
      <ShowCount />
      <Counter />
    </>
  );
}

Using Immer

Integrate with Immer for immutable state updates:

// stores/count.ts
import { create } from 'use-one';
import { produce } from 'immer';

const initialState = { count: 0 };
const [use, store] = create(initialState);

const computed = {
  get state() {
    return store.getState();
  },
};

const actions = {
  use,
  produce(cb: (state: typeof initialState) => void) {
    store.setState(produce(cb));
  },
  increment() {
    this.produce((state) => {
      state.count++;
    });
  },
  decrement() {
    this.produce((state) => {
      state.count--;
    });
  },
};

export const countStore = Object.assign(actions, computed, store);

Persisting Store State

For React Native or Expo applications, install @react-native-async-storage/async-storage first.

import { create, persistStore, wrapState, isClient } from 'use-one';

const initialState = wrapState({ count: 0 }); // Adds ready: false
const [use, store] = create(initialState);

if (isClient) {
  persistStore<typeof initialState>(store, {
    key: '@CACHE_KEY',
    debounce: 100, // Optional, defaults to 100ms
    transform: (state) => state, // Optional state transformer
  });
}

const actions = {
  use,
  get state() {
    return store.getState();
  },
  increment() {
    store.setState({ count: this.state.count + 1 });
  },
  decrement() {
    store.setState({ count: this.state.count - 1 });
  },
};

export const countStore = Object.assign(actions, store);

Persistence in SSR Applications

To prevent hydration errors in SSR applications (Next.js, Remix, etc.):

// store.ts
import {
  create,
  persistStore,
  wrapState,
  onPersistReady,
} from 'use-one';

const initialState = wrapState({ count: 0 });
const [use, store] = create(initialState);

onPersistReady(() => {
  persistStore<typeof initialState>(store, {
    key: '@CACHE_KEY',
    debounce: 100,
    transform: (state) => state,
  });
});

const actions = {
  use,
  get state() {
    return store.getState();
  },
  increment() {
    store.setState({ count: this.state.count + 1 });
  },
  decrement() {
    store.setState({ count: this.state.count - 1 });
  },
};

export const countStore = Object.assign(actions, store);
// layout.tsx
import { Provider as PersistProvider } from 'use-one';

export default function Layout({ children }: { children: React.ReactNode }) {
  return <PersistProvider>{children}</PersistProvider>;
}

Persisting Hook State

Persist any hook's state independently of stores:

import { useState } from 'react';
import { usePersist } from 'use-one';

export function Counter() {
  const [count, setCount] = useState(0);
  const [isReady, clean] = usePersist<typeof count>({
    key: '@count-store-key',
    getState: () => count,
    setState: setCount,
  });

  if (!isReady) return <div>Loading...</div>;

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(count - 1)}>-1</button>
      <button onClick={clean}>Clear Cache</button>
    </div>
  );
}

Advanced TypeScript Usage

Prevent property conflicts using StrictPropertyCheck:

import { create, type StrictPropertyCheck } from 'use-one';

const initialState = { count: 0 };
const [use, store] = create(initialState);

const _actions = {
  use,
  get state() {
    return store.getState();
  },
  increment() {
    store.setState({ count: this.state.count + 1 });
  },
  decrement() {
    store.setState({ count: this.state.count - 1 });
  },
};

const actions: StrictPropertyCheck<typeof _actions> = _actions;
export const countStore = Object.assign(actions, store);

API Reference

create<Type>(initialState, options?)

Creates a new store with the following options:

  • useEffect: Boolean (default: true) - Uses useEffect when true, useLayoutEffect when false
  • name: String - Optional name for the store

Returns [useHook, store] where store provides:

  • getState(): Get current state
  • setState(newState): Update state
  • forceUpdate(): Force component updates
  • subscribe(cb: (state) => void): Subscribe to state changes
  • syncState(newState): Update state without triggering updates
  • destroy(): Clean up store resources

Code Generation

Visit use-one-templates for boilerplate code generation tools, especially useful for managing multiple stores in larger applications.