Modern React development patterns including hooks, components, state management (Zustand, Redux Toolkit), and performance optimization. Activates when working with React components, JSX, hooks, stores, or React-specific architecture.
How this skill is triggered — by the user, by Claude, or both
Slash command
/react-frontend-bundle:react-patternsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill provides expert guidance on modern React development patterns and best practices.
This skill provides expert guidance on modern React development patterns and best practices.
Use these patterns when:
Functional Components First
Component Composition
// Good: Composable, reusable
function UserProfile({ user }) {
return (
<Card>
<Avatar src={user.avatar} />
<UserInfo name={user.name} email={user.email} />
<UserActions userId={user.id} />
</Card>
);
}
// Avoid: Monolithic, hard to test
function UserProfile({ user }) {
return (
<div>
{/* Everything in one component */}
</div>
);
}
State Management
useState for simple local stateuseReducer for complex state logicuseContext for shared state (sparingly)Effect Patterns
// Good: Proper dependency array
useEffect(() => {
fetchUser(userId);
}, [userId]);
// Good: Cleanup for subscriptions
useEffect(() => {
const subscription = api.subscribe(topic);
return () => subscription.unsubscribe();
}, [topic]);
Custom Hooks for Reusable Logic
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
When your app grows beyond simple local state, use external state management libraries.
Why Zustand:
Installation:
npm install zustand
Basic Store:
// stores/userStore.ts
import { create } from 'zustand';
interface User {
id: string;
name: string;
email: string;
}
interface UserState {
user: User | null;
isLoading: boolean;
error: string | null;
// Actions
setUser: (user: User) => void;
fetchUser: (id: string) => Promise<void>;
logout: () => void;
}
export const useUserStore = create<UserState>((set) => ({
user: null,
isLoading: false,
error: null,
setUser: (user) => set({ user }),
fetchUser: async (id) => {
set({ isLoading: true, error: null });
try {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
set({ user, isLoading: false });
} catch (error) {
set({ error: error.message, isLoading: false });
}
},
logout: () => set({ user: null }),
}));
Using the Store:
function UserProfile() {
// Subscribe to specific state slices
const user = useUserStore((state) => state.user);
const fetchUser = useUserStore((state) => state.fetchUser);
const isLoading = useUserStore((state) => state.isLoading);
useEffect(() => {
fetchUser('123');
}, [fetchUser]);
if (isLoading) return <Skeleton />;
return <div>{user?.name}</div>;
}
// Outside components
function logoutEverywhere() {
useUserStore.getState().logout();
}
Zustand with Immer (for complex state):
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
interface TodoState {
todos: Todo[];
addTodo: (text: string) => void;
toggleTodo: (id: string) => void;
}
export const useTodoStore = create<TodoState>()(
immer((set) => ({
todos: [],
addTodo: (text) =>
set((state) => {
// Mutate draft directly (Immer handles immutability)
state.todos.push({
id: crypto.randomUUID(),
text,
completed: false,
});
}),
toggleTodo: (id) =>
set((state) => {
const todo = state.todos.find((t) => t.id === id);
if (todo) todo.completed = !todo.completed;
}),
}))
);
Zustand with Persistence:
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export const useSettingsStore = create<SettingsState>()(
persist(
(set) => ({
theme: 'light',
language: 'en',
setTheme: (theme) => set({ theme }),
setLanguage: (language) => set({ language }),
}),
{
name: 'app-settings', // localStorage key
}
)
);
Zustand Slices Pattern (for large stores):
// stores/slices/userSlice.ts
export const createUserSlice = (set) => ({
user: null,
setUser: (user) => set({ user }),
});
// stores/slices/settingsSlice.ts
export const createSettingsSlice = (set) => ({
theme: 'light',
setTheme: (theme) => set({ theme }),
});
// stores/index.ts
import { create } from 'zustand';
export const useStore = create((set) => ({
...createUserSlice(set),
...createSettingsSlice(set),
}));
When to use Redux Toolkit:
Installation:
npm install @reduxjs/toolkit react-redux
Store Setup:
// store/userSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
export const fetchUser = createAsyncThunk(
'user/fetch',
async (userId: string) => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
);
const userSlice = createSlice({
name: 'user',
initialState: {
data: null,
loading: false,
error: null,
},
reducers: {
logout: (state) => {
state.data = null;
},
},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
},
});
export const { logout } = userSlice.actions;
export default userSlice.reducer;
// store/index.ts
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';
export const store = configureStore({
reducer: {
user: userReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
Using Redux:
// hooks/redux.ts
import { useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from '../store';
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppSelector = useSelector.withTypes<RootState>();
// Component
function UserProfile() {
const dispatch = useAppDispatch();
const { data: user, loading } = useAppSelector((state) => state.user);
useEffect(() => {
dispatch(fetchUser('123'));
}, [dispatch]);
return <div>{user?.name}</div>;
}
When to use Jotai:
Installation:
npm install jotai
Basic Atoms:
// atoms/user.ts
import { atom } from 'jotai';
export const userAtom = atom<User | null>(null);
export const isLoadingAtom = atom(false);
// Derived atom
export const userNameAtom = atom(
(get) => get(userAtom)?.name ?? 'Guest'
);
// Async atom
export const userDataAtom = atom(
async (get) => {
const response = await fetch('/api/user');
return response.json();
}
);
Using Atoms:
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
function UserProfile() {
const [user, setUser] = useAtom(userAtom);
const userName = useAtomValue(userNameAtom); // Read-only
const setLoading = useSetAtom(isLoadingAtom); // Write-only
return <div>{userName}</div>;
}
Do you need global state?
├─ No → Use useState/useReducer
└─ Yes
├─ Simple global state (settings, user, etc.)
│ └─ Use Zustand
├─ Complex enterprise app with time-travel debugging
│ └─ Use Redux Toolkit
└─ Need fine-grained reactivity and derived state
└─ Use Jotai
Example: Combining Zustand with React Query
// Server state with React Query
const { data: todos } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
});
// Client state with Zustand
const filter = useStore((state) => state.filter);
const setFilter = useStore((state) => state.setFilter);
// Combine for derived state
const filteredTodos = useMemo(
() => todos?.filter(todo =>
filter === 'all' || (filter === 'completed' && todo.completed)
),
[todos, filter]
);
Memoization Strategy
useMemo for expensive calculationsuseCallback for function references passed to childrenReact.memo for expensive components that render oftenWhen to Optimize
// Optimize when rendering is expensive
const ExpensiveList = React.memo(({ items }) => {
const sortedItems = useMemo(
() => items.sort((a, b) => a.priority - b.priority),
[items]
);
return sortedItems.map(item => <Item key={item.id} {...item} />);
});
Prop Design
interface ButtonProps {
variant: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
onClick: () => void;
children: React.ReactNode;
disabled?: boolean;
}
function Button({ variant, size = 'md', onClick, children, disabled }: ButtonProps) {
// Implementation
}
Recommended Organization (with Zustand)
src/
├── components/
│ ├── common/ # Reusable UI components
│ ├── features/ # Feature-specific components
│ └── layouts/ # Layout components
├── stores/ # Zustand stores
│ ├── userStore.ts
│ ├── settingsStore.ts
│ └── slices/ # Store slices for large apps
├── hooks/ # Custom hooks
├── contexts/ # Context providers (use sparingly)
├── utils/ # Helper functions
└── types/ # TypeScript types
// Presentational: Pure UI
function UserCard({ name, email, onEdit }) {
return (
<div>
<h3>{name}</h3>
<p>{email}</p>
<button onClick={onEdit}>Edit</button>
</div>
);
}
// Container: Logic and data
function UserCardContainer({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
const handleEdit = () => {
// Edit logic
};
return user ? <UserCard {...user} onEdit={handleEdit} /> : <Skeleton />;
}
function Select({ children, value, onChange }) {
return (
<select value={value} onChange={onChange}>
{children}
</select>
);
}
Select.Option = function Option({ value, children }) {
return <option value={value}>{children}</option>;
};
// Usage
<Select value={country} onChange={setCountry}>
<Select.Option value="us">United States</Select.Option>
<Select.Option value="ca">Canada</Select.Option>
</Select>
React.lazy() and SuspenseWhen reviewing React code, check for:
❌ Mutating state directly
// Wrong
state.items.push(newItem);
setState(state);
// Correct
setState({ ...state, items: [...state.items, newItem] });
❌ Using index as key in dynamic lists
// Wrong
{items.map((item, index) => <Item key={index} {...item} />)}
// Correct
{items.map(item => <Item key={item.id} {...item} />)}
❌ Over-using useEffect
// Wrong: Derived state in effect
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
// Correct: Calculate during render
const fullName = `${firstName} ${lastName}`;
npx claudepluginhub karchtho/my-claude-marketplace --plugin react-frontend-bundleGuides React state management with Redux Toolkit, Zustand, Jotai, and React Query. Use when setting up global state, managing server state, or choosing between solutions.
Guides React component design with minimal state via derived values and props, atomic memoization, type-safe patterns, and optimized conditional logic. Use for creating components, state management, render optimization.
Guides implementation of modern React patterns: hooks, component composition, state management, performance optimizations, concurrent features. Use for building or refactoring components.