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
queryFn
withTVariables
andTData
for input and output. onStore
ensures the fetched data aligns withTState
.- Lifecycle hooks (
onSuccess
,onError
, etc.) are typed to match the query’s data or errors.
- Strongly-typed
-
Mutation Typing:
mutationFn
enforces type safety for variables and responses.onStore
validates 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>;
};
count
is typed asnumber
.increment
is inferred as() => void
.- TypeScript catches errors if you try to assign
count
a string or callincrement
with 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>
);
};
cats
is typed asCat[]
.queryFn
returns{ data: Cat[] }
, and TypeScript enforces this structure.onStore
ensuresdata.data
matchesCat[]
.
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>;
};
variables
is typed as{ name: string }
.mutationFn
returns{ data: Cat }
, enforced by TypeScript.mutate
accepts 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 withmutationFn
andonStore
, ensuring type-safe async updates.QueryHook<TData, TVariables>
: Types the return value of a query (e.g.,isPending
,refetch
), withTVariables
for dynamic inputs.MutationHook<TData, TVariables>
: Types the mutation hook’s output (e.g.,mutate
,status
), aligning withTData
responses.
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.