From aai-dev-frontend
Provides patterns and code examples for managing local UI, shared, server (TanStack Query/SWR), URL (Next.js), and global (Zustand/Redux) state in React apps.
npx claudepluginhub bradtaylorsf/alphaagent-teamThis skill uses the workspace's default tool permissions.
Patterns for choosing and implementing state management strategies in frontend applications.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Guides code writing, review, and refactoring with Karpathy-inspired rules to avoid overcomplication, ensure simplicity, surgical changes, and verifiable success criteria.
Executes ctx7 CLI to fetch up-to-date library documentation, manage AI coding skills (install/search/generate/remove/suggest), and configure Context7 MCP. Useful for current API refs, skill handling, or agent setup.
Share bugs, ideas, or general feedback.
Patterns for choosing and implementing state management strategies in frontend applications.
State that only affects a single component.
// Form input values
const [value, setValue] = useState('');
// Toggle states
const [isOpen, setIsOpen] = useState(false);
// Loading/error states
const [status, setStatus] = useState<'idle' | 'loading' | 'error'>('idle');
State shared between components (lift state up or use context).
// Theme context
const ThemeContext = createContext<Theme>('light');
function ThemeProvider({ children }) {
const [theme, setTheme] = useState<Theme>('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
Data from external sources (use TanStack Query, SWR, or similar).
// TanStack Query
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 5 * 60 * 1000, // 5 minutes
});
// Mutations
const mutation = useMutation({
mutationFn: createUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
State that should be shareable/bookmarkable.
// Next.js App Router
import { useSearchParams, useRouter } from 'next/navigation';
function FilteredList() {
const searchParams = useSearchParams();
const router = useRouter();
const filter = searchParams.get('filter') || 'all';
const setFilter = (value: string) => {
const params = new URLSearchParams(searchParams);
params.set('filter', value);
router.push(`?${params.toString()}`);
};
return <Filters value={filter} onChange={setFilter} />;
}
| Use Case | Solution |
|---|---|
| Simple local state | useState |
| Complex local state | useReducer |
| Cross-component state | Context API |
| Global app state | Zustand, Redux Toolkit |
| Server state | TanStack Query, SWR |
| Form state | React Hook Form, Formik |
| URL state | Router params/search params |
import { create } from 'zustand';
interface AppState {
user: User | null;
setUser: (user: User | null) => void;
notifications: Notification[];
addNotification: (notification: Notification) => void;
removeNotification: (id: string) => void;
}
const useAppStore = create<AppState>((set) => ({
user: null,
setUser: (user) => set({ user }),
notifications: [],
addNotification: (notification) =>
set((state) => ({
notifications: [...state.notifications, notification],
})),
removeNotification: (id) =>
set((state) => ({
notifications: state.notifications.filter((n) => n.id !== id),
})),
}));
// Query configuration
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
gcTime: 5 * 60 * 1000, // 5 minutes
retry: 3,
refetchOnWindowFocus: false,
},
},
});
// Custom hook for domain logic
function useUsers(filters: UserFilters) {
return useQuery({
queryKey: ['users', filters],
queryFn: () => fetchUsers(filters),
select: (data) => data.users,
});
}
Compute values instead of storing them.
// Bad: Synced state
const [items, setItems] = useState([]);
const [total, setTotal] = useState(0);
useEffect(() => {
setTotal(items.reduce((sum, item) => sum + item.price, 0));
}, [items]);
// Good: Derived state
const [items, setItems] = useState([]);
const total = useMemo(
() => items.reduce((sum, item) => sum + item.price, 0),
[items]
);
For complex state transitions.
type Status = 'idle' | 'loading' | 'success' | 'error';
function useAsync<T>() {
const [state, setState] = useState<{
status: Status;
data: T | null;
error: Error | null;
}>({
status: 'idle',
data: null,
error: null,
});
const execute = async (promise: Promise<T>) => {
setState({ status: 'loading', data: null, error: null });
try {
const data = await promise;
setState({ status: 'success', data, error: null });
} catch (error) {
setState({ status: 'error', data: null, error: error as Error });
}
};
return { ...state, execute };
}
const mutation = useMutation({
mutationFn: updateTodo,
onMutate: async (newTodo) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: ['todos'] });
// Snapshot previous value
const previousTodos = queryClient.getQueryData(['todos']);
// Optimistically update
queryClient.setQueryData(['todos'], (old) =>
old.map((t) => (t.id === newTodo.id ? newTodo : t))
);
return { previousTodos };
},
onError: (err, newTodo, context) => {
// Rollback on error
queryClient.setQueryData(['todos'], context.previousTodos);
},
onSettled: () => {
// Always refetch after error or success
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
Is the state...
1. Used by only this component?
→ useState/useReducer
2. Shared by a few nearby components?
→ Lift state up to common ancestor
3. Used across many unrelated components?
→ Context or global store
4. From an external API?
→ TanStack Query/SWR
5. Should be in the URL?
→ Router state (params/search)
6. Complex with many transitions?
→ State machine (XState, useReducer)
Used by:
frontend-developer agent