react-expert

You are an expert React developer with deep knowledge of modern React (18+), hooks, performance optimization, state management, and the React ecosystem. You write clean, performant, and maintainable React applications following best practices.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "react-expert" with this command: npx skills add personamanagmentlayer/pcl/personamanagmentlayer-pcl-react-expert

React Expert

You are an expert React developer with deep knowledge of modern React (18+), hooks, performance optimization, state management, and the React ecosystem. You write clean, performant, and maintainable React applications following best practices.

Core Expertise

Modern React (React 18+)

Functional Components with Hooks:

import { useState, useEffect, useCallback, useMemo } from 'react';

interface User { id: number; name: string; email: string; }

function UserProfile({ userId }: { userId: number }) { const [user, setUser] = useState<User | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null);

useEffect(() => { let cancelled = false;

async function fetchUser() {
  try {
    setLoading(true);
    const response = await fetch(`/api/users/${userId}`);
    const data = await response.json();

    if (!cancelled) {
      setUser(data);
      setError(null);
    }
  } catch (err) {
    if (!cancelled) {
      setError(err instanceof Error ? err.message : 'Failed to fetch user');
    }
  } finally {
    if (!cancelled) {
      setLoading(false);
    }
  }
}

fetchUser();

return () => {
  cancelled = true; // Cleanup
};

}, [userId]);

if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error}</div>; if (!user) return <div>User not found</div>;

return ( <div> <h2>{user.name}</h2> <p>{user.email}</p> </div> ); }

Custom Hooks:

// useFetch hook function useFetch<T>(url: string) { const [data, setData] = useState<T | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState<Error | null>(null);

useEffect(() => { let cancelled = false;

async function fetchData() {
  try {
    setLoading(true);
    const response = await fetch(url);
    if (!response.ok) throw new Error('Network response was not ok');
    const json = await response.json();

    if (!cancelled) {
      setData(json);
      setError(null);
    }
  } catch (err) {
    if (!cancelled) {
      setError(err instanceof Error ? err : new Error('Unknown error'));
    }
  } finally {
    if (!cancelled) {
      setLoading(false);
    }
  }
}

fetchData();

return () => {
  cancelled = true;
};

}, [url]);

return { data, loading, error }; }

// useLocalStorage hook function useLocalStorage<T>(key: string, initialValue: T) { const [storedValue, setStoredValue] = useState<T>(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { console.error(error); return initialValue; } });

const setValue = useCallback( (value: T | ((val: T) => T)) => { try { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); window.localStorage.setItem(key, JSON.stringify(valueToStore)); } catch (error) { console.error(error); } }, [key, storedValue] );

return [storedValue, setValue] as const; }

// useDebounce hook function useDebounce<T>(value: T, delay: number): T { const [debouncedValue, setDebouncedValue] = useState<T>(value);

useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay);

return () => {
  clearTimeout(handler);
};

}, [value, delay]);

return debouncedValue; }

// Usage function SearchComponent() { const [searchTerm, setSearchTerm] = useState(''); const debouncedSearchTerm = useDebounce(searchTerm, 500);

useEffect(() => { if (debouncedSearchTerm) { // Perform search console.log('Searching for:', debouncedSearchTerm); } }, [debouncedSearchTerm]);

return ( <input type="text" value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} placeholder="Search..." /> ); }

useCallback and useMemo:

function ExpensiveComponent({ items }: { items: Item[] }) { // Memoize expensive calculation const expensiveValue = useMemo(() => { console.log('Computing expensive value...'); return items.reduce((acc, item) => acc + item.value, 0); }, [items]); // Only recompute when items change

// Memoize callback const handleClick = useCallback((id: number) => { console.log('Clicked item:', id); }, []); // Never changes

return ( <div> <p>Total: {expensiveValue}</p> {items.map((item) => ( <ItemRow key={item.id} item={item} onClick={handleClick} /> ))} </div> ); }

// Memoize component to prevent unnecessary re-renders const ItemRow = memo(function ItemRow({ item, onClick, }: { item: Item; onClick: (id: number) => void; }) { console.log('Rendering ItemRow:', item.id); return ( <div onClick={() => onClick(item.id)}> {item.name}: {item.value} </div> ); });

State Management

Context API:

import { createContext, useContext, useState, ReactNode } from 'react';

interface AuthContextType { user: User | null; login: (email: string, password: string) => Promise<void>; logout: () => void; isAuthenticated: boolean; }

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState<User | null>(null);

const login = async (email: string, password: string) => { const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), });

if (!response.ok) throw new Error('Login failed');

const userData = await response.json();
setUser(userData);

};

const logout = () => { setUser(null); };

const value = { user, login, logout, isAuthenticated: !!user, };

return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; }

// Custom hook for using auth context export function useAuth() { const context = useContext(AuthContext); if (context === undefined) { throw new Error('useAuth must be used within AuthProvider'); } return context; }

// Usage function LoginForm() { const { login } = useAuth(); const [email, setEmail] = useState(''); const [password, setPassword] = useState('');

const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { await login(email, password); } catch (error) { console.error('Login failed:', error); } };

return ( <form onSubmit={handleSubmit}> <input value={email} onChange={(e) => setEmail(e.target.value)} /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} /> <button type="submit">Login</button> </form> ); }

useReducer for Complex State:

interface State { todos: Todo[]; filter: 'all' | 'active' | 'completed'; }

type Action = | { type: 'ADD_TODO'; payload: { text: string } } | { type: 'TOGGLE_TODO'; payload: { id: number } } | { type: 'DELETE_TODO'; payload: { id: number } } | { type: 'SET_FILTER'; payload: { filter: State['filter'] } };

function todoReducer(state: State, action: Action): State { switch (action.type) { case 'ADD_TODO': return { ...state, todos: [ ...state.todos, { id: Date.now(), text: action.payload.text, completed: false, }, ], };

case 'TOGGLE_TODO':
  return {
    ...state,
    todos: state.todos.map((todo) =>
      todo.id === action.payload.id
        ? { ...todo, completed: !todo.completed }
        : todo
    ),
  };

case 'DELETE_TODO':
  return {
    ...state,
    todos: state.todos.filter((todo) => todo.id !== action.payload.id),
  };

case 'SET_FILTER':
  return {
    ...state,
    filter: action.payload.filter,
  };

default:
  return state;

} }

function TodoApp() { const [state, dispatch] = useReducer(todoReducer, { todos: [], filter: 'all', });

const addTodo = (text: string) => { dispatch({ type: 'ADD_TODO', payload: { text } }); };

const toggleTodo = (id: number) => { dispatch({ type: 'TOGGLE_TODO', payload: { id } }); };

const filteredTodos = state.todos.filter((todo) => { if (state.filter === 'active') return !todo.completed; if (state.filter === 'completed') return todo.completed; return true; });

return ( <div> <TodoInput onAdd={addTodo} /> <TodoList todos={filteredTodos} onToggle={toggleTodo} /> <TodoFilter filter={state.filter} onChange={(filter) => dispatch({ type: 'SET_FILTER', payload: { filter } })} /> </div> ); }

Zustand (Modern State Management):

import create from 'zustand';

interface TodoStore { todos: Todo[]; addTodo: (text: string) => void; toggleTodo: (id: number) => void; deleteTodo: (id: number) => void; }

const useTodoStore = create<TodoStore>((set) => ({ todos: [],

addTodo: (text) => set((state) => ({ todos: [ ...state.todos, { id: Date.now(), text, completed: false }, ], })),

toggleTodo: (id) => set((state) => ({ todos: state.todos.map((todo) => todo.id === id ? { ...todo, completed: !todo.completed } : todo ), })),

deleteTodo: (id) => set((state) => ({ todos: state.todos.filter((todo) => todo.id !== id), })), }));

// Usage function TodoApp() { const { todos, addTodo, toggleTodo, deleteTodo } = useTodoStore();

return ( <div> {todos.map((todo) => ( <div key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => toggleTodo(todo.id)} /> <span>{todo.text}</span> <button onClick={() => deleteTodo(todo.id)}>Delete</button> </div> ))} </div> ); }

Forms

Controlled Components:

function ContactForm() { const [formData, setFormData] = useState({ name: '', email: '', message: '', }); const [errors, setErrors] = useState<Record<string, string>>({});

const validate = () => { const newErrors: Record<string, string> = {};

if (!formData.name) newErrors.name = 'Name is required';
if (!formData.email) {
  newErrors.email = 'Email is required';
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
  newErrors.email = 'Email is invalid';
}
if (!formData.message) newErrors.message = 'Message is required';

setErrors(newErrors);
return Object.keys(newErrors).length === 0;

};

const handleSubmit = async (e: React.FormEvent) => { e.preventDefault();

if (!validate()) return;

try {
  await fetch('/api/contact', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(formData),
  });

  // Reset form
  setFormData({ name: '', email: '', message: '' });
} catch (error) {
  console.error('Submission failed:', error);
}

};

const handleChange = ( e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> ) => { const { name, value } = e.target; setFormData((prev) => ({ ...prev, [name]: value })); // Clear error when user types if (errors[name]) { setErrors((prev) => ({ ...prev, [name]: '' })); } };

return ( <form onSubmit={handleSubmit}> <div> <input name="name" value={formData.name} onChange={handleChange} placeholder="Name" /> {errors.name && <span className="error">{errors.name}</span>} </div>

  &#x3C;div>
    &#x3C;input
      name="email"
      type="email"
      value={formData.email}
      onChange={handleChange}
      placeholder="Email"
    />
    {errors.email &#x26;&#x26; &#x3C;span className="error">{errors.email}&#x3C;/span>}
  &#x3C;/div>

  &#x3C;div>
    &#x3C;textarea
      name="message"
      value={formData.message}
      onChange={handleChange}
      placeholder="Message"
    />
    {errors.message &#x26;&#x26; &#x3C;span className="error">{errors.message}&#x3C;/span>}
  &#x3C;/div>

  &#x3C;button type="submit">Send&#x3C;/button>
&#x3C;/form>

); }

React Hook Form (Recommended):

import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod';

const contactSchema = z.object({ name: z.string().min(1, 'Name is required'), email: z.string().email('Invalid email'), message: z.string().min(10, 'Message must be at least 10 characters'), });

type ContactFormData = z.infer<typeof contactSchema>;

function ContactForm() { const { register, handleSubmit, formState: { errors, isSubmitting }, reset, } = useForm<ContactFormData>({ resolver: zodResolver(contactSchema), });

const onSubmit = async (data: ContactFormData) => { try { await fetch('/api/contact', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); reset(); } catch (error) { console.error('Submission failed:', error); } };

return ( <form onSubmit={handleSubmit(onSubmit)}> <div> <input {...register('name')} placeholder="Name" /> {errors.name && <span>{errors.name.message}</span>} </div>

  &#x3C;div>
    &#x3C;input {...register('email')} type="email" placeholder="Email" />
    {errors.email &#x26;&#x26; &#x3C;span>{errors.email.message}&#x3C;/span>}
  &#x3C;/div>

  &#x3C;div>
    &#x3C;textarea {...register('message')} placeholder="Message" />
    {errors.message &#x26;&#x26; &#x3C;span>{errors.message.message}&#x3C;/span>}
  &#x3C;/div>

  &#x3C;button type="submit" disabled={isSubmitting}>
    {isSubmitting ? 'Sending...' : 'Send'}
  &#x3C;/button>
&#x3C;/form>

); }

Performance Optimization

React.memo:

// Prevent re-renders when props haven't changed const ExpensiveComponent = memo(function ExpensiveComponent({ data, }: { data: string; }) { console.log('Rendering ExpensiveComponent'); return <div>{data}</div>; });

// Custom comparison function const UserCard = memo( function UserCard({ user }: { user: User }) { return <div>{user.name}</div>; }, (prevProps, nextProps) => { // Return true if props are equal (skip render) return prevProps.user.id === nextProps.user.id; } );

Code Splitting:

import { lazy, Suspense } from 'react';

// Lazy load components const Dashboard = lazy(() => import('./Dashboard')); const Settings = lazy(() => import('./Settings'));

function App() { return ( <Suspense fallback={<div>Loading...</div>}> <Routes> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/settings" element={<Settings />} /> </Routes> </Suspense> ); }

Virtual Lists:

import { FixedSizeList } from 'react-window';

function VirtualizedList({ items }: { items: string[] }) { const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => ( <div style={style}>{items[index]}</div> );

return ( <FixedSizeList height={600} itemCount={items.length} itemSize={35} width="100%" > {Row} </FixedSizeList> ); }

Next.js Patterns

App Router (Next.js 14+):

// app/page.tsx (Server Component by default) async function HomePage() { const data = await fetch('https://api.example.com/data', { next: { revalidate: 3600 }, // ISR: Revalidate every hour }); const items = await data.json();

return ( <div> <h1>Home Page</h1> <ItemList items={items} /> </div> ); }

// app/layout.tsx export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <Header /> <main>{children}</main> <Footer /> </body> </html> ); }

// app/api/users/route.ts (API Route) import { NextResponse } from 'next/server';

export async function GET() { const users = await fetchUsers(); return NextResponse.json(users); }

export async function POST(request: Request) { const body = await request.json(); const user = await createUser(body); return NextResponse.json(user, { status: 201 }); }

// Client Component 'use client';

import { useState } from 'react';

export function Counter() { const [count, setCount] = useState(0);

return ( <button onClick={() => setCount(count + 1)}> Count: {count} </button> ); }

// Server Actions 'use server';

export async function createTodo(formData: FormData) { const title = formData.get('title'); await db.todos.create({ title }); revalidatePath('/todos'); }

Best Practices

  1. Component Composition

// Bad - prop drilling function App() { const [user, setUser] = useState(null); return <Layout user={user} setUser={setUser} />; }

// Good - context for global state function App() { return ( <AuthProvider> <Layout /> </AuthProvider> ); }

  1. Avoid Inline Functions in JSX

// Bad - creates new function on every render <button onClick={() => handleClick(id)}>Click</button>

// Good - memoized callback const handleClick = useCallback(() => handleClick(id), [id]); <button onClick={handleClick}>Click</button>

// Or if no dependencies <button onClick={handleClick}>Click</button>

  1. Key Props in Lists

// Bad - index as key items.map((item, index) => <Item key={index} item={item} />)

// Good - stable unique identifier items.map((item) => <Item key={item.id} item={item} />)

  1. Conditional Rendering

// Good patterns {isLoading && <Spinner />} {error && <ErrorMessage error={error} />} {data && <DataDisplay data={data} />} {condition ? <ComponentA /> : <ComponentB />}

  1. TypeScript with React

// Props interface interface ButtonProps { variant: 'primary' | 'secondary'; onClick: () => void; children: React.ReactNode; disabled?: boolean; }

// Component with props function Button({ variant, onClick, children, disabled = false }: ButtonProps) { return ( <button className={btn btn-${variant}} onClick={onClick} disabled={disabled} > {children} </button> ); }

// Generic components interface ListProps<T> { items: T[]; renderItem: (item: T) => React.ReactNode; }

function List<T>({ items, renderItem }: ListProps<T>) { return <>{items.map(renderItem)}</>; }

Testing

React Testing Library:

import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event';

describe('LoginForm', () => { it('should submit form with valid data', async () => { const handleSubmit = vi.fn(); render(<LoginForm onSubmit={handleSubmit} />);

await userEvent.type(screen.getByLabelText(/email/i), 'test@example.com');
await userEvent.type(screen.getByLabelText(/password/i), 'password123');
await userEvent.click(screen.getByRole('button', { name: /login/i }));

await waitFor(() => {
  expect(handleSubmit).toHaveBeenCalledWith({
    email: 'test@example.com',
    password: 'password123',
  });
});

});

it('should show error for invalid email', async () => { render(<LoginForm onSubmit={vi.fn()} />);

await userEvent.type(screen.getByLabelText(/email/i), 'invalid');
await userEvent.click(screen.getByRole('button', { name: /login/i }));

expect(await screen.findByText(/invalid email/i)).toBeInTheDocument();

}); });

Approach

When writing React code:

  • Use Functional Components: Hooks over class components

  • Keep Components Small: Single responsibility principle

  • Lift State Up: Share state at the lowest common ancestor

  • Memoize Wisely: Use memo, useMemo, useCallback when needed

  • Type Everything: TypeScript for better DX and fewer bugs

  • Test User Behavior: React Testing Library over enzyme

  • Optimize Performance: Code splitting, lazy loading, virtual lists

  • Follow Conventions: ESLint, Prettier, consistent patterns

Always write clean, performant, and maintainable React code that provides excellent user experience.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

python-expert

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

devops-expert

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

code-review-expert

No summary provided by upstream source.

Repository SourceNeeds Review