Skip to main content
state-management Use when choosing state management solutions, implementing global stores (Zustand, Pinia), managing server state (TanStack Query), or handling URL state in frontend applications across React and Vue.
Jump to install Skills Marketplace Discover and explore AI skills built by the community.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Copy promptShow prompt details A direct command skips the review prompt. Inspect the source before running it.
npx skills add https://github.com/MadAppGang/claude-code --skill state-managementThe command stays on one line. Scroll horizontally to inspect it before copying.
Prefer a local copy? Download the files currently available to SkillsMP.
Download Zip Downloading... Related occupations
SOC
Based on SOC occupation classification
More from this repository name state-management version 1.0.0 description Use when choosing state management solutions, implementing global stores (Zustand, Pinia), managing server state (TanStack Query), or handling URL state in frontend applications across React and Vue. keywords ["state management","Zustand","Pinia","TanStack Query","server state","global state","URL state","React context"] plugin dev updated "2026-01-20T00:00:00.000Z"
Frontend State Management
Overview
Patterns and best practices for managing state in frontend applications across different frameworks.
State Categories
Local vs Global State
Type Scope Examples Solution Local UI Single component Form inputs, modals, dropdowns useState, ref
Server Cached API data Users, products, orders TanStack Query, SWR
Global App Entire app Auth, settings, notifications Zustand, Pinia, Redux
URL Browser URL Filters, pagination, search Router params/query
When to Use What ┌─────────────────────────────────────────────────────────┐
│ Does only this component need it? │
│ YES → Local state (useState/ref) │
│ NO ↓ │
├─────────────────────────────────────────────────────────┤
│ Is it server data that needs caching/sync? │
│ YES → Server state library (TanStack Query) │
│ NO ↓ │
├─────────────────────────────────────────────────────────┤
│ Is it in the URL (shareable state)? │
│ YES → URL state (router) │
│ NO ↓ │
├─────────────────────────────────────────────────────────┤
│ Is it needed across unrelated components? │
│ YES → Global store (Zustand/Pinia) │
│ NO → Lift state up or Context │
└─────────────────────────────────────────────────────────┘
Server State (TanStack Query)
Basic Query Pattern
function useUsers (filters : UserFilters ) {
return useQuery ({
queryKey : ['users' , filters],
queryFn : () => api.getUsers (filters),
staleTime : 5 * 60 * 1000 ,
gcTime : 30 * 60 * 1000 ,
});
}
function UserList ( ) {
const [filters, setFilters] = useState<UserFilters >({});
const { data, isLoading, error } = useUsers (filters);
if (isLoading) return <Spinner /> ;
if (error) return <Error message ={error.message} /> ;
return <List items ={data} /> ;
}
Mutation Pattern function useCreateUser ( ) {
const queryClient = useQueryClient ();
return useMutation ({
mutationFn : (data : CreateUserInput ) => api.createUser (data),
onSuccess : () => {
queryClient.invalidateQueries ({ queryKey : ['users' ] });
},
});
}
const createUser = useCreateUser ();
await createUser.mutateAsync ({ name : 'John' , email : 'john@example.com' });
Optimistic Updates function useUpdateUser ( ) {
const queryClient = useQueryClient ();
return useMutation ({
mutationFn : (data : UpdateUserInput ) => api.updateUser (data),
onMutate : async (newData) => {
await queryClient.cancelQueries ({ queryKey : ['user' , newData.id ] });
const previous = queryClient.getQueryData (['user' , newData.id ]);
queryClient.setQueryData (['user' , newData.id ], (old : User ) => ({
...old,
...newData,
}));
return { previous };
},
onError : (err, newData, context ) => {
queryClient.setQueryData (['user' , newData.id ], context?.previous );
},
onSettled : () => {
queryClient.invalidateQueries ({ queryKey : ['users' ] });
},
});
}
Global State (Zustand)
Store Definition interface AppStore {
theme : 'light' | 'dark' ;
sidebarOpen : boolean ;
notifications : Notification [];
setTheme : (theme : 'light' | 'dark' ) => void ;
toggleSidebar : () => void ;
addNotification : (notification : Notification ) => void ;
removeNotification : (id : string ) => void ;
}
export const useAppStore = create<AppStore >((set ) => ({
theme : 'light' ,
sidebarOpen : true ,
notifications : [],
setTheme : (theme ) => set ({ theme }),
toggleSidebar : () => set ((state ) => ({ sidebarOpen : !state.sidebarOpen })),
addNotification : (notification ) =>
set ((state ) => ({
notifications : [...state.notifications , notification],
})),
removeNotification : (id ) =>
set ((state ) => ({
notifications : state.notifications .filter ((n ) => n.id !== id),
})),
}));
Selectors for Performance
const { theme, notifications } = useAppStore ();
const theme = useAppStore ((state ) => state.theme );
const { theme, sidebarOpen } = useAppStore (
(state ) => ({ theme : state.theme , sidebarOpen : state.sidebarOpen }),
shallow
);
Computed/Derived State const useAppStore = create<AppStore >((set, get ) => ({
notifications : [],
unreadCount : () => get ().notifications .filter ((n ) => !n.read ).length ,
}));
const unreadCount = useAppStore ((state ) =>
state.notifications .filter ((n ) => !n.read ).length
);
Global State (Pinia for Vue) export const useAppStore = defineStore ('app' , () => {
const theme = ref<'light' | 'dark' >('light' );
const sidebarOpen = ref (true );
const notifications = ref<Notification []>([]);
const unreadCount = computed (() =>
notifications.value .filter ((n ) => !n.read ).length
);
function setTheme (newTheme : 'light' | 'dark' ) {
theme.value = newTheme;
}
function toggleSidebar ( ) {
sidebarOpen.value = !sidebarOpen.value ;
}
return {
theme,
sidebarOpen,
notifications,
unreadCount,
setTheme,
toggleSidebar,
};
});
URL State
Search Params
function useSearchParams ( ) {
const [searchParams, setSearchParams] = useSearchParams ();
const filters = useMemo (
() => ({
page : parseInt (searchParams.get ('page' ) || '1' ),
search : searchParams.get ('search' ) || '' ,
sort : searchParams.get ('sort' ) || 'name' ,
}),
[searchParams]
);
const setFilters = (newFilters : Partial <typeof filters> ) => {
setSearchParams ((prev ) => {
Object .entries (newFilters).forEach (([key, value] ) => {
if (value) prev.set (key, String (value));
else prev.delete (key);
});
return prev;
});
};
return [filters, setFilters] as const ;
}
Benefits of URL State
Shareable links
Browser back/forward works
Bookmarkable
SEO-friendly
Survives refresh
Best Practices
1. Colocate State Keep state as close to where it's used as possible.
const useGlobalStore = create (() => ({
isModalOpen : false ,
toggleModal : () => {},
}));
function UserProfile ( ) {
const [isModalOpen, setModalOpen] = useState (false );
}
2. Single Source of Truth Don't duplicate state across stores.
const authStore = { user : { id : 1 , name : 'John' } };
const profileStore = { user : { id : 1 , name : 'John' } };
const authStore = { userId : 1 };
const usersCache = { 1 : { id : 1 , name : 'John' } };
3. Derive Don't Store Compute derived data instead of storing it.
const store = {
items : [],
itemCount : 0 ,
filteredItems : [],
totalPrice : 0 ,
};
const store = {
items : [],
};
const itemCount = items.length ;
const filteredItems = items.filter (predicate);
const totalPrice = items.reduce ((sum, i ) => sum + i.price , 0 );
4. Normalize Complex Data
const store = {
orders : [
{
id : 1 ,
user : { id : 1 , name : 'John' },
items : [{ id : 1 , product : { id : 1 , name : 'Widget' } }],
},
],
};
const store = {
orders : { 1 : { id : 1 , userId : 1 , itemIds : [1 ] } },
users : { 1 : { id : 1 , name : 'John' } },
items : { 1 : { id : 1 , productId : 1 , orderId : 1 } },
products : { 1 : { id : 1 , name : 'Widget' } },
};
5. Handle Loading/Error States interface AsyncState <T> {
data : T | null ;
loading : boolean ;
error : Error | null ;
}
function UserList ({ state }: { state: AsyncState<User[]> } ) {
if (state.loading ) return <Spinner /> ;
if (state.error ) return <Error error ={state.error} /> ;
if (!state.data ?.length ) return <Empty /> ;
return <List items ={state.data} /> ;
}
Anti-Patterns
1. Prop Drilling (Instead: Context or Store)
<App theme={theme}>
<Layout theme ={theme} >
<Sidebar theme ={theme} >
<MenuItem theme ={theme} /> // 4 levels deep!
</Sidebar >
</Layout >
</App >
const theme = useTheme ();
2. Storing Server Data in Global State
const store = {
users : [],
fetchUsers : async () => {
const users = await api.getUsers ();
set ({ users });
},
};
const { data : users } = useQuery (['users' ], api.getUsers );
3. Mutating State Directly
state.users .push (newUser);
state.users [0 ].name = 'Updated' ;
set ({ users : [...state.users , newUser] });
set ({
users : state.users .map ((u ) =>
u.id === id ? { ...u, name : 'Updated' } : u
),
});
State management patterns for frontend applications