Store
Local State Management

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