use-one
v2.0.0-alpha.2
Published
Use-one.js: A simple state management lib for React.js
Maintainers
Readme
Use-one.js
Use-one.js: A simple state management lib for React.js
Introduction
use-one is a lightweight and simple 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 --savepnpm
pnpm install use-oneUsage
Basic Example
// stores/count.ts
import { create } from 'use-one';
const initialState = { count: 0 };
export const [use, store] = create(initialState);
const actions = {
get state() {
return store.getState();
},
increment() {
store.setState({ count: this.state.count + 1 });
},
decrement() {
store.setState({ count: this.state.count - 1 });
},
};
export const useCount = use;
export const countStore = Object.assign(actions, store);Using the Store
// CountExample.tsx
import * as React from 'react';
import { useCount, countStore } from './stores/count';
const Counter = () => {
const [{ count }] = useCount();
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] = useCount();
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 = {
produce(cb: (state: typeof initialState) => void) {
store.setState(produce(cb));
},
increment() {
this.produce((state) => {
state.count++;
});
},
decrement() {
this.produce((state) => {
state.count--;
});
},
};
export const useCount = use;
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
encode: (state) => JSON.stringify(state), // Optional
decode: (state) => JSON.Parse(state), // Optional
});
}
const actions = {
get state() {
return store.getState();
},
increment() {
store.setState({ count: this.state.count + 1 });
},
decrement() {
store.setState({ count: this.state.count - 1 });
},
};
export const useCount = use;
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,
encode: (state) => JSON.stringify(state), // Optional
decode: (state) => JSON.Parse(state), // Optional
});
});
const actions = {
get state() {
return store.getState();
},
increment() {
store.setState({ count: this.state.count + 1 });
},
decrement() {
store.setState({ count: this.state.count - 1 });
},
};
export const useCount = use;
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 = {
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 useCount = use;
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) - UsesuseEffectwhen true,useLayoutEffectwhen falsename: String - Optional name for the store
Returns [useHook, store] where store provides:
getState(): Get current statesetState(newState): Update stateforceUpdate(): Force component updatessubscribe(cb: (state) => void): Subscribe to state changessyncState(newState): Update state without triggering updatesdestroy(): Clean up store resources
Code Generation
Visit use-one-templates for boilerplate code generation tools, especially useful for managing multiple stores in larger applications.
