React Query in MUZE
The frontend uses TanStack React Query v5 for all server state management. This replaces traditional Redux/Zustand patterns for API data.Provider setup
App.tsx together with SessionProvider and the toast mount point.
Documentation Index
Fetch the complete documentation index at: /llms.txt
Use this file to discover all available pages before exploring further.
How the frontend uses TanStack React Query for server state management
// frontend/src/App.tsx
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 2, // 2 minutes
retry: 1,
refetchOnWindowFocus: false,
},
},
});
App.tsx together with SessionProvider and the toast mount point.
// Custom hook using React Query
export function useEmployees(storeId?: string) {
return useQuery({
queryKey: ['employees', storeId],
queryFn: () => api.get(`/employees`, { params: { storeId } }),
enabled: !!storeId,
});
}
export function useCreateEmployee() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateEmployeeDto) =>
api.post('/employees', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['employees'] });
},
});
}
export function useUpdateOrderStatus() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, status }: { id: string; status: OrderStatus }) =>
api.patch(`/orders/${id}/status`, { status }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['orders'] });
},
});
}
| Action | Invalidation |
|---|---|
| Create entity | Invalidate list query for that entity type |
| Update entity | Invalidate both list and detail queries |
| Delete entity | Invalidate list query |
| Import CSV | Invalidate list query |
| Status change | Invalidate order list and detail queries |
// List queries
['employees', storeId]
['orders', { storeId, status }]
['rule-sets', clientId]
// Detail queries
['employee', employeeId]
['order', orderId]
// Aggregation queries
['reports', 'store-ops', { dateRange }]
['entitlements', employeeId, categoryId]
const { data, error, isLoading } = useEmployees();
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
if (!data) return <EmptyState />;
return <EmployeeTable employees={data} />;
return useMutation({
mutationFn: updateOrderStatus,
onMutate: async (newStatus) => {
await queryClient.cancelQueries({ queryKey: ['orders'] });
const previous = queryClient.getQueryData(['orders']);
queryClient.setQueryData(['orders'], (old) =>
old.map(order => order.id === newStatus.id
? { ...order, status: newStatus.status }
: order
)
);
return { previous };
},
onError: (err, newStatus, context) => {
queryClient.setQueryData(['orders'], context.previous);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['orders'] });
},
});
| State | Managed by |
|---|---|
| Authentication session | better-auth + local state |
| UI state (modals, drawers) | React component state |
| Form state | React Hook Form |
| Theme / dark mode | CSS variables + React context |
| Global app settings | React context |
Was this page helpful?