React

React hooks, patterns, and best practices for building modern UIs.

frameworks
reactjavascriptfrontendhooks

Component Basics

// Functional Component
function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}

// Arrow Function Component
const Greeting = ({ name }) => {
  return <h1>Hello, {name}!</h1>;
};

// With default props
const Button = ({ text = 'Click me', onClick }) => {
  return <button onClick={onClick}>{text}</button>;
};

// Children prop
const Card = ({ children, title }) => {
  return (
    <div className="card">
      <h2>{title}</h2>
      {children}
    </div>
  );
};

useState Hook

import { useState } from 'react';

function Counter() {
  // Basic state
  const [count, setCount] = useState(0);
  
  // State with object
  const [user, setUser] = useState({ name: '', age: 0 });
  
  // Update state
  const increment = () => setCount(count + 1);
  
  // Functional update (for state based on previous)
  const incrementSafe = () => setCount(prev => prev + 1);
  
  // Update object state
  const updateName = (name) => {
    setUser(prev => ({ ...prev, name }));
  };
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
    </div>
  );
}

useEffect Hook

import { useEffect, useState } from 'react';

function DataFetcher({ userId }) {
  const [data, setData] = useState(null);
  
  // Run on every render
  useEffect(() => {
    console.log('Rendered');
  });
  
  // Run once on mount
  useEffect(() => {
    console.log('Mounted');
    return () => console.log('Unmounted'); // Cleanup
  }, []);
  
  // Run when dependency changes
  useEffect(() => {
    fetchUser(userId).then(setData);
  }, [userId]);
  
  // Async effect
  useEffect(() => {
    const fetchData = async () => {
      const result = await fetchUser(userId);
      setData(result);
    };
    fetchData();
  }, [userId]);
  
  return <div>{data?.name}</div>;
}

useContext Hook

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

// Create context
const ThemeContext = createContext();

// Provider component
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Custom hook for consuming context
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

// Usage in component
function ThemedButton() {
  const { theme, toggleTheme } = useTheme();
  return <button onClick={toggleTheme}>{theme}</button>;
}

useRef Hook

import { useRef, useEffect } from 'react';

function TextInput() {
  // DOM reference
  const inputRef = useRef(null);
  
  // Mutable value that persists
  const renderCount = useRef(0);
  
  useEffect(() => {
    inputRef.current.focus(); // Access DOM element
    renderCount.current += 1;
  });
  
  return <input ref={inputRef} />;
}

useMemo & useCallback

import { useMemo, useCallback } from 'react';

function ExpensiveComponent({ items, onItemClick }) {
  // Memoize expensive computation
  const sortedItems = useMemo(() => {
    return [...items].sort((a, b) => a.name.localeCompare(b.name));
  }, [items]);
  
  // Memoize callback function
  const handleClick = useCallback((id) => {
    onItemClick(id);
  }, [onItemClick]);
  
  return (
    <ul>
      {sortedItems.map(item => (
        <li key={item.id} onClick={() => handleClick(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

useReducer Hook

import { useReducer } from 'react';

// Reducer function
function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD':
      return [...state, { id: Date.now(), text: action.text, done: false }];
    case 'TOGGLE':
      return state.map(todo =>
        todo.id === action.id ? { ...todo, done: !todo.done } : todo
      );
    case 'DELETE':
      return state.filter(todo => todo.id !== action.id);
    default:
      return state;
  }
}

function TodoList() {
  const [todos, dispatch] = useReducer(todoReducer, []);
  
  return (
    <>
      <button onClick={() => dispatch({ type: 'ADD', text: 'New todo' })}>
        Add
      </button>
      {todos.map(todo => (
        <div key={todo.id}>
          <span 
            style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
            onClick={() => dispatch({ type: 'TOGGLE', id: todo.id })}
          >
            {todo.text}
          </span>
          <button onClick={() => dispatch({ type: 'DELETE', id: todo.id })}>
            Delete
          </button>
        </div>
      ))}
    </>
  );
}

Custom Hooks

// useLocalStorage hook
function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });
  
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);
  
  return [value, setValue];
}

// useDebounce hook
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);
  
  return debouncedValue;
}

// useFetch hook
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const controller = new AbortController();
    
    fetch(url, { signal: controller.signal })
      .then(res => res.json())
      .then(setData)
      .catch(setError)
      .finally(() => setLoading(false));
    
    return () => controller.abort();
  }, [url]);
  
  return { data, loading, error };
}

Event Handling

function Form() {
  const [value, setValue] = useState('');
  
  // Input change
  const handleChange = (e) => {
    setValue(e.target.value);
  };
  
  // Form submit
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(value);
  };
  
  // Click with data
  const handleClick = (id) => (e) => {
    console.log(id, e);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input value={value} onChange={handleChange} />
      <button type="submit">Submit</button>
      <button onClick={handleClick(123)}>Click</button>
    </form>
  );
}

Conditional Rendering

function Component({ isLoggedIn, items, user }) {
  return (
    <>
      {/* If condition */}
      {isLoggedIn && <Dashboard />}
      
      {/* If-else */}
      {isLoggedIn ? <Dashboard /> : <Login />}
      
      {/* Multiple conditions */}
      {items.length === 0 && <p>No items</p>}
      {items.length > 0 && items.length <= 5 && <p>Few items</p>}
      {items.length > 5 && <p>Many items</p>}
      
      {/* Nullish check */}
      {user?.name ?? 'Anonymous'}
    </>
  );
}

Lists & Keys

function ItemList({ items }) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

// With index (only if items have no stable ID)
function SimpleList({ items }) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
}