1kb subscription-based state hooks for React
yarn add @novas/substate
Allow child components to create and subscribe to keyed stateful values on a parent ContextProvider and re-render only when necessary, without memoization at the component level. 1kb and no dependencies.
import {
useSubState,
useCreateSubState,
SubStateProvider,
} from '@novas/substate'
import { useRef, useEffect } from 'react'
const Parent = ({ children }) => {
const api = useCreateSubState()
return <SubStateProvider value={api}>{children}</SubStateProvider>
}
const Child = ({ id, children }) => {
const { state, setState, store } = useSubState(id)
const renderCount = useRef(0)
useEffect(() => void renderCount.current++)
return (
<p>
<h2>Child {id}</h2>
<span>Render count: {renderCount.current}</span>
<span>Current value: {state}</span>
<button onClick={() => setState(Math.random())}>
Re-render child {id}
</button>
</p>
)
}
const Page = () => (
<Parent>
<Child id="1" />
<Child id="2" />
<Child id="3" />
<Child id={3} />
</Parent>
)
The store must be an object. Do not mutate state directly, use the setter functions.
useCreateSubState
to create the store api and pass it to aSubStateProvider
import { useCreateSubState, SubStateProvider } from '@novas/substate'
const MyComponent = ({ children }) => {
const api = useCreateSubState()
return <SubStateProvider value={api}>{children}</SubStateProvider>
}
useSubState
to create and subscribe to keyed values in the store from a child component. It will only cause re-renders when the store value with that specific key changes. Multiple components can subscribe to the same key.
const { state, setState } = useSubState('test')
setState
accepts a value or merging function, just like React.
setState({ hello: 'world' })
setState((currentState) => currentState++)
useSubState
also returns the entire store and store update function.
const { state, setState, store, setStore } = useSubState('test')
// state === store.test
setStore
accepts a key and a value or merging function. Astring
key will update a single key in the store, whileundefined
will update the entire store. Updating the entire store will re-render all components subscribed to that store. The store must be an object.
setStore('test', { hello: 'world' })
setStore(undefined, { test: { hello: 'world' } })
useSubState
without a key will subscribe to the entire store. This will cause re-renders any time a value in the store changes, and allow setting the entire store withsetState
. The store must be an object.
const { state, setState, store, setStore } = useSubState()
// state === store
// setState === ((value) => setStore(undefined, value))
useSubState
accepts an optional initial value. If two components with the same key have different initial values, the component that mounts later will overwrite the first. This works for individual keys, or the entire store. The store must be an object.
const { state: count, setState: setCount } = useSubState('test', 2)
const { store, setStore } = useSubState(undefined, { test: 1 })