Overview
createScopedStore creates a store that is scoped to a specific React component tree using React Context. This allows you to have multiple instances of the same store in different parts of your application, each with independent state.
Signature
function createScopedStore<TState extends object>(
initialState: TState
): {
StoreProvider: FunctionComponent<StoreProviderProps<TState>>
useScopedStore: () => Store<TState>
withStore: <TProps extends object>(
Component: FunctionComponent<TProps>,
initialValue?: Partial<TState>
) => (props: TProps) => JSX.Element
useStore: () => TState
useStoreEffect: (run: (state: TState) => void, deps: DependencyList) => void
}
Parameters
The initial state object for the store. This defines the shape and default values for all store instances.
Returns
An object containing:
StoreProvider
FunctionComponent<StoreProviderProps<TState>>
A React component that provides the store to its children via Context. See StoreProvider.
A hook that returns the complete store instance from the nearest StoreProvider. Useful for accessing the raw store API.
A Higher-Order Component that wraps a component with a StoreProvider. See withStore.
A scoped version of useStore that reads from the nearest StoreProvider context.
useStoreEffect
(run: (state: TState) => void, deps: DependencyList) => void
A scoped version of useStoreEffect that works with the nearest StoreProvider context.
Usage
Basic Example
import { createScopedStore } from 'stan-js'
interface UserFormState {
name: string
email: string
isDirty: boolean
}
const {
StoreProvider,
useStore,
useStoreEffect
} = createScopedStore<UserFormState>({
name: '',
email: '',
isDirty: false
})
function FormField() {
const { name, actions } = useStore()
return (
<input
value={name}
onChange={(e) => actions.setName(e.target.value)}
/>
)
}
function App() {
return (
<div>
<StoreProvider initialValue={{ name: 'Alice' }}>
<FormField />
</StoreProvider>
<StoreProvider initialValue={{ name: 'Bob' }}>
<FormField />
</StoreProvider>
</div>
)
}
Multiple Independent Instances
const { StoreProvider, useStore } = createScopedStore({
count: 0,
label: 'Counter'
})
function Counter() {
const { count, label, actions } = useStore()
return (
<div>
<h3>{label}</h3>
<button onClick={() => actions.setCount(count + 1)}>
Count: {count}
</button>
</div>
)
}
function App() {
return (
<>
{/* Each counter has independent state */}
<StoreProvider initialValue={{ label: 'First' }}>
<Counter />
</StoreProvider>
<StoreProvider initialValue={{ label: 'Second' }}>
<Counter />
</StoreProvider>
</>
)
}
Using useScopedStore
const { StoreProvider, useScopedStore } = createScopedStore({
items: [] as string[]
})
function ItemList() {
const store = useScopedStore()
// Access raw store API
const addItem = (item: string) => {
store.actions.setItems([...store.state.items, item])
}
// Subscribe to specific state changes
useEffect(() => {
const unsubscribe = store.subscribe((state) => {
console.log('Items changed:', state.items)
})
return unsubscribe
}, [])
return (
<div>
{store.state.items.map((item, i) => (
<div key={i}>{item}</div>
))}
</div>
)
}
Implementation Details
React Context
The implementation creates a React Context using createContext from React:
const StoreContext = createContext(createStore(initialState))
The context’s display name is set to 'stan-js' for easier debugging in React DevTools.
Store Isolation
Each StoreProvider creates its own store instance:
const [store] = useState(() => createStore(mergeState(initialState, initialValue ?? {})))
This ensures complete isolation between different parts of your component tree.
Hook Implementation
All scoped hooks use useContext to access the nearest store:
const useStore = () => {
const { useStore } = useContext(StoreContext)
return useStore()
}
Common Patterns
const { StoreProvider, useStore } = createScopedStore({
formData: {},
errors: {},
isSubmitting: false
})
function EditUserForm({ userId }: { userId: string }) {
return (
<StoreProvider>
<FormFields />
<FormActions />
</StoreProvider>
)
}
Modal State
const { StoreProvider, useStore, withStore } = createScopedStore({
isOpen: false,
data: null as any
})
const Modal = withStore(ModalContent, { isOpen: false })
Nested Contexts
function NestedExample() {
return (
<ParentStoreProvider>
{/* Child has access to parent store */}
<ChildStoreProvider>
{/* Can use both stores here */}
</ChildStoreProvider>
</ParentStoreProvider>
)
}
See Also