use-one.js
is a simple state management lib for React.js.
Features
- No more complex concepts, only hooks
- Easy share state anywhere
- Easy persist store or your hooks state
- Tiny size (gzip ~2KB)
- Write in TypeScript, Typesafe
npm
npm install use-one --save
pnpm
pnpm install use-one
// stores/count.ts
import { create, EventBus, eventBus } 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);
Use the hook
// CountExample.tsx
import * as React from 'react';
import { countStore } from './stores/count';
const Counter = () => {
const [state] = countStore.use();
const { count } = state;
// const { count } = countStore.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 ShowCountInOtherPlace = () => {
const [state] = countStore.use();
return <span>Count: {state.count}</span>;
};
export default function App() {
return (
<React.Fragment>
<ShowCount />
<Counter />
</React.Fragment>
);
}
We can wrap a new function that call produceState
with immer's produce
, for example:
export function produceState(cb: (state: typeof initialState) => void) {
countStore.setState(produce(cb));
}
Full code:
// 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);
If you are using React-Native or Expo, Need install
@react-native-async-storage/async-storage
import { create, persistStore, wrapState, isClient } from 'use-one';
const initialState = wrapState({ count: 0 }); // -> { ready: false, count: 0 }
const [use, store] = create(initialState);
console.log('isClient', isClient);
isClient &&
persistStore<typeof initialState>(store, {
key: '@CACHE_KEY',
debounce: 100, // optional, default 100ms
transform: (state) => state, // optional, transform the state before to `setState`
});
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);
To prevent hydration error in SSR application(like Next.js, Remix..etc.), we can do this:
-
- Use
onPersistReady
to subscribe ready event to persist:
- Use
import {
create,
persistStore,
wrapState,
isClient,
onPersistReady,
} from 'use-one';
const initialState = wrapState({ count: 0 }); // -> { ready: false, count: 0 }
const [use, store] = create(initialState);
onPersistReady(() => {
persistStore<typeof initialState>(store, {
key: '@CACHE_KEY',
debounce: 100, // optional, default 100ms
transform: (state) => state, // optional, transform the state before to `setState`
});
});
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);
-
- Add
PersistProvider
to your components to emit ready event:
- Add
import { Provider as PersistProvider } from 'use-one';
export default function Layout({ children }: { children: React.ReactNode }) {
return <PersistProvider>{children}</PersistProvider>;
}
This is a helper function, no relation with store.
You persist any hooks state. For example, let's persist useState:
If you are using React-Native or Expo, Need install
@react-native-async-storage/async-storage
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,
// setState: (state) => setCount(state),
});
if (!isReady) return <div>Loading</div>;
return (
<div>
<h1>{count}</h1>
<br />
<button onClick={() => setCount(count + 1)}>+1</button>
<br />
<button onClick={() => setCount(count - 1)}>-1</button>
<br />
<button onClick={clean}>Clean Cache</button>
</div>
);
}
create
- e.g:create<Type>(initialState, Options?: {useEffect?: boolean, name?: string})
if the options useEffect is false, will use useLayoutEffect- returns
[useHook, store]
store
methods:.getState()
get the state.setState(newState)
set the state.forceUpdate()
force update.subscribe(cb: (state) => {})
subscribe.setState
update, return unsubscribe function.syncState(newState)
sync state without update.destroy
clear event
- returns
Check use-one-templates, it's very useful to create many share states in large application.