Type Improvements
Why TypeScript Matters
Zustand Q is built with TypeScript at its core, providing a robust and type-safe experience that enhances developer productivity and reduces runtime errors. While vanilla Zustand offers basic TypeScript support, Zustand Q takes it further by introducing detailed type definitions for state, actions, queries, and mutations. This ensures early error detection, better IDE support (e.g., autocompletion, refactoring), and a more maintainable codebase—especially in large-scale applications with complex async workflows.
Key Type Features
Zustand Q’s type system offers the following improvements:
-
State Typing (
TState):- Define the exact shape of your store’s state with a custom interface or type.
- Prevents invalid state updates and ensures consistency across your app.
-
Action Typing (
TActions):- Type-safe action functions with full inference for parameters and return types.
- Guarantees that actions match the state structure they modify.
-
Query Typing:
- Strongly-typed
queryFnwithTVariablesandTDatafor input and output. onStoreensures the fetched data aligns withTState.- Lifecycle hooks (
onSuccess,onError, etc.) are typed to match the query’s data or errors.
- Strongly-typed
-
Mutation Typing:
mutationFnenforces type safety for variables and responses.onStorevalidates that mutation results integrate correctly with the state.- Return values (e.g.,
mutate,status) are fully typed for predictable behavior.
These features make zustand-q a powerful choice for TypeScript developers, bridging the gap between simplicity and safety.
Usage Examples
Basic State and Actions
Define a typed store with state and actions:
import { createStore } from "zustand-q";
interface CounterState {
count: number;
}
interface CounterActions {
increment: () => void;
decrement: () => void;
}
export const useCounterStore = createStore<CounterState, CounterActions>({
initialData: { count: 0 },
actions: (set) => ({
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}),
});
const App = () => {
const { count, increment } = useCounterStore();
return <button onClick={increment}>Count: {count}</button>;
};
countis typed asnumber.incrementis inferred as() => void.- TypeScript catches errors if you try to assign
counta string or callincrementwith arguments.
Queries with Async Data
Fetch a list of cats with typed queries:
import { createStore } from "zustand-q";
interface Cat {
id: string;
name: string;
}
interface CatState {
cats: Cat[];
}
export const useCatStore = createStore({
initialData: { cats: [] } as CatState,
queries: {
getCatList: {
queryFn: async () => ({
data: [{ id: "1", name: "Mimi" }] as { data: Cat[] },
}),
onStore: (data, set) => set({ cats: data.data }),
onSuccess: (data) => console.log("Fetched cats:", data.data),
},
},
});
const App = () => {
const { cats, getCatList } = useCatStore();
const { refetch } = getCatList({ enabled: true });
return (
<ul>
{cats.map((cat) => (
<li key={cat.id}>{cat.name}</li>
))}
</ul>
);
};
catsis typed asCat[].queryFnreturns{ data: Cat[] }, and TypeScript enforces this structure.onStoreensuresdata.datamatchesCat[].
Mutations with Type Safety
Add a cat with a typed mutation:
import { createStore } from "zustand-q";
interface Cat {
id: string;
name: string;
}
interface CatState {
cats: Cat[];
}
export const useCatStore = createStore({
initialData: { cats: [] } as CatState,
mutations: {
addCat: {
mutationFn: async (variables: { name: string }) =>
({ data: { id: "2", name: variables.name } } as { data: Cat }),
onStore: (data, set) =>
set((state) => ({ cats: [...state.cats, data.data] })),
},
},
});
const App = () => {
const { addCat } = useCatStore();
const { mutate: createCat } = addCat();
return <button onClick={() => createCat({ name: "Lulu" })}>Add Cat</button>;
};
variablesis typed as{ name: string }.mutationFnreturns{ data: Cat }, enforced by TypeScript.mutateaccepts only{ name: string }, preventing invalid calls.
Type Definitions
Zustand Q provides comprehensive type definitions to support its features. Key types include:
QueryConfig<TData, TVariables, TState>: Defines the structure of a query, includingqueryFn(TVariables => Promise<TData>),onStore(TData => TState), and lifecycle hooks.MutationConfig<TData, TVariables, TState>: Shapes a mutation withmutationFnandonStore, ensuring type-safe async updates.QueryHook<TData, TVariables>: Types the return value of a query (e.g.,isPending,refetch), withTVariablesfor dynamic inputs.MutationHook<TData, TVariables>: Types the mutation hook’s output (e.g.,mutate,status), aligning withTDataresponses.
Explore the full definitions in the source code.
Benefits
- Error Prevention: Catch type mismatches at compile time (e.g., passing a number to a string field).
- IDE Support: Get autocompletion and hover documentation in editors like VS Code.
- Scalability: Maintain type consistency as your app grows, especially with complex async logic.
Zustand Q’s type improvements make it a standout choice for TypeScript-first projects, offering both simplicity and safety.