TypeScript
Type a store, get the tools and actions types, and split actions across files without circular types.
The library is written in TypeScript and ships its own types. State, selectors and actions are inferred from
what you pass. The site checks these snippets with astro check and uses expectTypeOf assertions in
the tests.
Type the state
typed-store.ts
import { createGlobalState } from 'react-global-state-hooks';
interface User {
id: string;
name: string;
}
// Without options, pass the state type as a type argument.
export const useUser = createGlobalState<User | null>(null);
interface Session {
user: User | null;
status: 'idle' | 'loading' | 'error';
}
// With options, annotate the initial value instead. A type argument plus options does not compile,
// because that overload has three type parameters.
const initial: Session = { user: null, status: 'idle' };
export const useSession = createGlobalState(initial, {
actions: {
start() {
return ({ setState }) => {
setState((session) => ({ ...session, status: 'loading' }));
};
},
signIn(user: User) {
return ({ setState }) => {
setState({ user, status: 'idle' });
};
},
},
});Helper types
| Type | Gives you |
|---|---|
InferAPI<typeof storeOrContext> | The store tools type for a global store, a context, or the object returned by createContext. |
InferStateApi<typeof useStore> | The store tools of a global store. |
InferContextApi<typeof Ctx.Context> | The store tools of a context. |
InferActionsType<typeof useStore> | The type of the actions object. |
The store tools type contains getState, setState, metadata, setMetadata, subscribe and actions.
Split actions into templates
Lifecycle callbacks such as onInit receive tools whose actions are typed loosely, because the actions are
still being defined. Get the exact type with InferAPI, define actions against it, and bind them later.
split-actions.ts
import { actions, createGlobalState, type InferAPI } from 'react-global-state-hooks';
export const useCounter = createGlobalState(
{ count: 0 },
{
actions: {
increment() {
return ({ setState }) => {
setState((state) => ({ count: state.count + 1 }));
};
},
},
},
);
// The exact tools type of this store: state, actions, metadata and the store methods.
type CounterAPI = InferAPI<typeof useCounter>;
// A template typed against the store, defined before it is bound to anything.
export const extraActions = actions<CounterAPI>()({
reset() {
return ({ setState }) => {
setState({ count: 0 });
};
},
incrementTwice() {
return ({ actions }) => {
actions.increment();
actions.increment();
};
},
});
// Bind it to the store when you need it.
export const bound = extraActions(useCounter);Contexts
context-types.tsx
import { createContext, type InferAPI } from 'react-global-state-hooks';
export const FormContext = createContext({ name: '', email: '' });
// Works with the object returned by createContext, or with its `Context`.
export type FormApi = InferAPI<typeof FormContext>;
export function resetForm(api: FormApi) {
api.setState({ name: '', email: '' });
}What to watch for
- The types do not verify that every action key returns a function. A wrong action fails when it is called, with an error that names the key.
- Persistence options are typed loosely (
localStorage?: LocalStorageConfig<Any, Any>). Type yourvalidatorarguments yourself when you need the state type inside it, or let a schema do it:z.infer<typeof schema>gives you the state type andschema.parsegives you the validator.