Local State Management
The createStoreContext
helper makes locally scoped state easy and computed properties, actions, and effects are automatically scoped to the local store instance.
However, it is essential to encapsulate your store definition within the .computed
, .actions
,
and .effects
methods. If you create any computed values/actions/effects elsewhere, they will not be scoped to the local store instance.
Creating a Store Context
To create a store context, use the createStoreContext
function and pass in the global store:
import { store, createStoreContext } from '@davstack/store';
const counterStore = store({
count: 0,
}).actions((store) => ({
increment: () => store.count.set(store.count.get() + 1),
}));
export const {
useStore: useCounterStore,
Provider: CounterStoreProvider,
withProvider: withCounterStoreProvider,
} = createStoreContext(counterStore);
Usage with Provider
const Counter = ({ customProp }: { customProp: string }) => {
const counterStore = useCounterStore();
const count = counterStore.count.use();
return (
<div>
<p>Count: {count}</p>
<button onClick={counterStore.increment}>Increment</button>
</div>
);
};
const App = () => {
return (
<>
<CounterStoreProvider initialState={{ count: 1 }}>
<Counter customProp="example" />
</CounterStoreProvider>
<CounterStoreProvider initialState={{ count: 5 }}>
<Counter />
</CounterStoreProvider>
</>
);
};
Usage with withProvider
const Counter = withCounterStoreProvider(
({ customProp }: { customProp: string }) => {
const counterStore = useCounterStore();
const count = counterStore.count.use();
return (
<div>
<p>Count: {count}</p>
<button onClick={counterStore.increment}>Increment</button>
</div>
);
}
);
const App = () => {
return (
<>
<Counter initialState={{ count: 1 }} customProp="example" />
<Counter initialState={{ count: 5 }} customProp="example" />
</>
);
};
Merging Local and Global State
When creating a local instance of a store with <Provider initialState={...}>
, the initialState
will be merged with the global store's initial value:
const globalStore = store({
count: 0,
name: 'John',
});
const storeContext = createStoreContext(globalStore);
const App = () => {
return (
<storeContext.Provider initialState={{ count: 5 }}>
{/* count will be 5, but name will still be 'John' from the global store */}
<Counter />
</storeContext.Provider>
);
};
In this example, the local instance of the store will have a count
of 5, but the name
property will still be "John" from the global store.
What's going on inside the provider?
The provider will automatically subscribe to all effects on mount and unsubscribe from all effects on unmount.
Each provider will create a new store instance, so be careful when nesting providers to avoid creating unnecessary store instances.
For example
function WrapperComponent(props: { children: ReactNode }) {
return <>{children}</>;
}
const WrapperWithProvider = myStore.withProvider(WrapperComponent);
function App() {
<>
// one store is created here
<WrapperWithProvider>
// another store is created here
<WrapperWithProvider>
<div />
</WrapperWithProvider>
</WrapperWithProvider>
</>;
}
Is it possible to create a local store, without creating a global store?
Yes this is actually the default behaviour. This is because the store
function does NOT create a store instance until a non-builder method is called.
For example, calling store().state().computed()
will NOT create a global store because all the methods called are the store builder methods.
However, once a non-builder method is called then the store will check if an instance already exists and if it does not then a global store instance will be created.
For example, state methods such as get
set
use
, or custom actions/computed values, are non-builder methods, so calling them will implicity create a global instance if it does not exist.
const counterStore = store({ count: 1 });
// no global store instance just yet
counterStore.count.get();
// a global store instance is created if it didn't previously exist