Help us improve
Share bugs, ideas, or general feedback.
From react-rtk
React component patterns, hooks rules, composition patterns. Use when editing .tsx/.jsx files, working in components/ or hooks/ directories, or creating new React components.
npx claudepluginhub surfertas/claude-react-rtk --plugin react-rtkHow this skill is triggered — by the user, by Claude, or both
Slash command
/react-rtk:react-patternsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
- Functional components ONLY (no class components in new code)
Guides modern React patterns: hooks, composition, performance, TypeScript best practices, state management selection, and React 19 features.
Provides React best practices for function components, props interfaces, compound components, useState, useEffect hooks, and state management. Useful for optimizing React code architecture and performance.
Guides React developers in using composition patterns to scale components, avoid boolean props, and design reusable APIs. Covers compound components, context providers, and React 19 changes.
Share bugs, ideas, or general feedback.
use prefix, return typed tuple or objectuseEffect cleanup: always clean up subscriptions, timers, AbortControllersuseEffect deps: include ALL values from component scope that change over timeuseState vs useReducer: 3+ related state values → useReducer// ✅ Compound component
<Tabs defaultValue="profile">
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="profile">...</Tabs.Content>
<Tabs.Content value="settings">...</Tabs.Content>
</Tabs>
// ✅ Render prop for flexibility
<DataFetcher url="/api/users">
{({ data, loading, error }) => (
loading ? <Skeleton /> : <UserList users={data} />
)}
</DataFetcher>
// ✅ Custom hook extraction
function useUsers(filters: UserFilters) {
const { data, isLoading, error } = useGetUsersQuery(filters);
const sortedUsers = useMemo(() =>
data ? [...data].sort(sortByName) : [],
[data]
);
return { users: sortedUsers, isLoading, error };
}
// ✅ Lazy state initialization
// BAD: runs JSON.parse every render
const [config] = useState(JSON.parse(localStorage.getItem('config') ?? '{}'));
// GOOD: function form runs only once
const [config] = useState(() => JSON.parse(localStorage.getItem('config') ?? '{}'));
// ✅ Derived state inline (not in useEffect)
// BAD: useEffect to sync derived value
const [fullName, setFullName] = useState('');
useEffect(() => { setFullName(`${first} ${last}`) }, [first, last]);
// GOOD: compute during render
const fullName = `${first} ${last}`;
// ✅ Move effects to event handlers
// BAD: effect reacts to state change
const [submitted, setSubmitted] = useState(false);
useEffect(() => { if (submitted) sendForm(data) }, [submitted]);
// GOOD: logic in handler
const handleSubmit = () => { sendForm(data) };
// ✅ Functional setState (avoids stale closures)
// BAD: stale closure risk
const add = useCallback(() => setItems([...items, newItem]), [items, newItem]);
// GOOD: stable callback
const add = useCallback(() => setItems(prev => [...prev, newItem]), [newItem]);
// architecture-avoid-boolean-props: DON'T add boolean variant props
// BAD: boolean props accumulate and create implicit coupling
<Card isCompact isAdmin isHighlighted />
// GOOD: explicit composition
<CompactCard>
<AdminBadge />
<HighlightedContent>...</HighlightedContent>
</CompactCard>
// architecture-compound-components: shared context for multi-part UI
// Use for Tabs, Accordion, Menu, Combobox, etc.
const TabsContext = createContext<TabsState | null>(null);
function Tabs({ defaultValue, children }: TabsProps) {
const [active, setActive] = useState(defaultValue);
return (
<TabsContext value={{ active, setActive }}>
{children}
</TabsContext>
);
}
Tabs.List = TabsList;
Tabs.Trigger = TabsTrigger;
Tabs.Content = TabsContent;
// state-context-interface: generic context shape for DI-friendly providers
interface ContextValue<T> {
state: T;
actions: Record<string, (...args: any[]) => void>;
meta: { loading: boolean; error: Error | null };
}
// state-lift-state: siblings share state → lift to provider, not prop drill
// BAD: prop drilling through intermediate components
<Parent data={data} onUpdate={onUpdate}>
<MiddleLayer data={data} onUpdate={onUpdate}>
<ChildA data={data} />
<ChildB onUpdate={onUpdate} />
// GOOD: context provider eliminates drilling
<DataProvider>
<MiddleLayer>
<ChildA /> {/* reads from context */}
<ChildB /> {/* dispatches via context */}
// react19-no-forwardref: ref is a regular prop in React 19+
// BAD (React 18): forwardRef wrapper
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => (
<input ref={ref} {...props} />
));
// GOOD (React 19+): ref as regular prop
function Input({ ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}
// use() replaces useContext()
// BAD (React 18)
const theme = useContext(ThemeContext);
// GOOD (React 19+) — works in conditionals and loops
const theme = use(ThemeContext);
// rerender-defer-reads: don't subscribe to state only used in callbacks
// BAD: component re-renders on every count change
function Logger() {
const count = useAppSelector(state => state.counter.value);
const handleClick = () => console.log(count);
return <button onClick={handleClick}>Log</button>;
}
// GOOD: read inside callback — no subscription, no re-render
function Logger() {
const store = useStore();
const handleClick = () => console.log(store.getState().counter.value);
return <button onClick={handleClick}>Log</button>;
}
// rerender-derived-state: subscribe to derived booleans, not raw objects
// BAD: re-renders whenever ANY user field changes
const user = useAppSelector(state => state.auth.user);
if (user?.role === 'admin') { /* ... */ }
// GOOD: re-renders only when admin status actually changes
const isAdmin = useAppSelector(state => state.auth.user?.role === 'admin');
// rerender-memo-with-default-value: hoist non-primitive defaults
// BAD: new array every render → breaks memo/effect deps
function List({ items = [] }: { items?: Item[] }) { /* ... */ }
// GOOD: stable reference
const EMPTY_ITEMS: Item[] = [];
function List({ items = EMPTY_ITEMS }: { items?: Item[] }) { /* ... */ }
// rerender-transitions: non-urgent updates → startTransition
import { startTransition } from 'react';
const handleSearch = (query: string) => {
setQuery(query); // urgent: update input
startTransition(() => {
setFilteredResults(filterItems(query)); // non-urgent: can defer
});
};
// rerender-use-ref-transient-values: frequently-changing non-render values
// BAD: setState for every mouse move → re-render storm
const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
// GOOD: ref for values not used in render output
const mousePosRef = useRef({ x: 0, y: 0 });
useEffect(() => {
const handler = (e: MouseEvent) => {
mousePosRef.current = { x: e.clientX, y: e.clientY };
};
window.addEventListener('mousemove', handler);
return () => window.removeEventListener('mousemove', handler);
}, []);
For detailed patterns, see references/ directory.