yo-store
v1.3.3
Published
Simple state management for React
Downloads
28
Readme
yo-store
It is a simple state manager in React without context or third-party libraries.
Installation:
npm install yo-store
or
yarn add yo-store
Usage :
Add a storage
to your component:
// news-storage.js
import createStorage from "yo-storage";
const useNews = createStorage((set, get) => ({
selected: undefined,
filters: {
page: 1,
page_size: 25,
}
getPageSize: ()=> get().filters.page_size
setSelected: (item)=> set({selected: item})
getNews: async ()=> fetch(".../news")
}),
(state) => {
console.log("This is a middleware: ", state)
}
);
export default useNews
// news-list.js
import { isEqual } from 'yo-store'
import useNews from "storage/new-storage"
/// subscribe with selector to a specific store change (subscribe function return a unsubscribe function)
// in this case when `selected` is changed then subscribe callback is executed
const unsubscribe = useNews.subscribe(
(state)=> state.selected
(selected)=> {
if(selected?.status === "Active") {
// do something
}
})
/// subscribe to any change of storage
const unsubscribe = useNews.subscribe(null, (state)=> {
if(state.selected?.status === "Active") {
// do something
}
})
const unsubscribe = useNews.subscribe(
(state) => ({ selected: state.selected, filters: state.filters }),
(filtered) => {
console.log('your filtered values are: ', filtered)
},
isEqual,
)
// you can retrieve state values anywhere
const PAGE_SIZE = useNews.getState().filters.page_size;
// you can use a state selector function.
// this ensures that the component is rerendered only when the selected states change.
function selector(state){
return { selected: state.selected, setSelected: state.setSelected };
}
function News({ newsObject }){
const { selected, setSelected } = useNews(selector)
const newsNameRef = useRef(selected?.name)
// Connect to the store on mount, disconnect on unmount, catch state-changes in a reference
useEffect(()=> useNews.subscribe((state) => state.selected.name, (name)=> {
newsNameRef.current = name
}), []);
return (
<div>
...
<h1>{selected?.description}</h1>
...
<button onClick={()=> {
// you can set the status anywhere
setSelected(newsObject)
}}>
Set News
</button>
<button onClick={()=> {
// you can set the status anywhere
useNews.setState({ selected: null })
}}>
Clear News
</button>
</div>
)
}
No installation :
If you do not want to install it, you can copy and paste the source code into your project.
import { useMemo, useSyncExternalStore } from 'react'
import isEqual from './is-equal'
export { default as isEqual } from './is-equal'
type GetState<T> = () => T
type SetValueSlice<T> = Partial<T> | ((state: T) => Partial<T>)
type SetState<T> = (value: SetValueSlice<T>) => void
type StoreValues<T> = (set: (value: SetValueSlice<T>) => void, get: GetState<T>) => T
type Middleware<T> = (state: T, newValues: Partial<T>, set: SetState<T>, get: GetState<T>) => void
function storeApi<T>(values: T | StoreValues<T>, middleware?: Middleware<T>) {
const subscribers = new Set<(data: T) => void>()
function subscribe(callback: (data: T) => void): () => void {
subscribers.add(callback)
return () => subscribers.delete(callback)
}
const get: GetState<T> = () => store
const set: SetState<T> = (value) => {
const newValue = typeof value === 'function' ? (value as (state: T) => T)(store) : value
store = { ...store, ...newValue }
middleware?.(store, newValue, set, get)
subscribers.forEach((callback) => callback(store))
}
let store = typeof values === 'function' ? (values as StoreValues<T>)(set, get) : values
return { subscribe, set, get }
}
export default function createStore<T>(values: T | StoreValues<T>, middleware?: Middleware<T>) {
const api = storeApi(values, middleware)
// this is a similar implementation to the one in the zustand library (subscribeWithSelector middleware)
function subscribeWithSelector<Selector>(
selector: (state: T) => Selector,
callback?: (currentValue: Selector, previousValue: Selector) => void,
equalityFn?: (a: any, b: any) => boolean,
) {
if (callback) {
let currentValue = selector?.(api.get()) || api.get()
const listener = (state: T) => {
const nextValue = selector?.(state) || state
const isEqualFn = equalityFn || Object.is
if (!isEqualFn(currentValue, nextValue)) {
const previousValue = currentValue
callback((currentValue = nextValue) as Selector, previousValue as Selector)
}
}
return api.subscribe(listener)
}
return api.subscribe(selector)
}
function useStore<Selector>(selector: (state: T) => Selector, equalityFn = isEqual): Selector {
const handleSelector = useMemo(() => {
let hasMemoizedValue = false
let memoizedValue: Selector
const memoizedSelector = () => {
const nextValue = selector(api.get())
if (!hasMemoizedValue) {
hasMemoizedValue = true
memoizedValue = nextValue
} else if (!equalityFn(memoizedValue, nextValue)) {
memoizedValue = nextValue
}
return memoizedValue
}
return memoizedSelector
}, [equalityFn, selector])
const state = useSyncExternalStore(api.subscribe, handleSelector, handleSelector)
return state as Selector
}
useStore.subscribe = subscribeWithSelector
useStore.getState = api.get
useStore.setState = api.set
return useStore
}