Full Stack Learning Hub

Comprehensive guides, cheat sheets, and code examples for full stack development.

View on GitHub

React Basics Guide

React is a JavaScript library for building user interfaces, primarily based on Components and State.

Quick Reference

Concept Description Example
Component Reusable UI piece function Button() { ... }
JSX HTML-in-JavaScript syntax return <div>Hello</div>;
Props Data passed down to components <Welcome name="Alice" />
State Data managed inside a component const [count, setCount] = useState(0);
Hooks Functions to hook into React features useState, useEffect

1. Components & JSX

React apps are built from components. A component is a JavaScript function that returns markup (JSX).

Basic Component

// App.js
function Welcome({ name }) {
  return <h1>Hello, {name}</h1>;
}

export default function App() {
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

Rules of JSX:

  1. Return a single root element: Wrap adjacent elements in a <div> or Fragment <>...</>.
  2. Close all tags: <img /> (self-closing), not <img>.
  3. camelCase properties: className instead of class, onClick instead of onclick.

2. Props (Passing Data)

Props are read-only. They allow parent components to pass data down to children.

function UserProfile({ user }) {
  return (
    <div className="card">
      <img src={user.avatarUrl} alt={user.name} />
      <h3>{user.name}</h3>
    </div>
  );
}

// Usage
<UserProfile user= />

3. State (Interactivity)

State allows components to “remember” things (e.g., current input value, open/closed modal).

useState Hook

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0); // [currentValue, updateFunction]

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      Clicked {count} times
    </button>
  );
}

Lifting State Up

When multiple components need to share and modify the same state, lift that state up to their closest common ancestor.

Why Lift State?

// Parent holds shared state
function Parent() {
  const [sharedValue, setSharedValue] = useState('');

  return (
    <>
      <ChildA value={sharedValue} onChange={setSharedValue} />
      <ChildB value={sharedValue} />
    </>
  );
}

// Child A can modify state via callback
function ChildA({ value, onChange }) {
  return <input value={value} onChange={e => onChange(e.target.value)} />;
}

// Child B receives state as prop
function ChildB({ value }) {
  return <p>Current value: {value}</p>;
}
Scenario Action
Two siblings need same data Lift to parent
Child needs to update parent’s display Pass callback prop
Deeply nested components share state Consider Context API

See also: React Context State Management Guide

Controlled Components

A controlled component is a form element whose value is controlled by React state.

Type Value Source When to Use
Controlled React state Validation, conditional logic, format enforcement
Uncontrolled DOM directly Simple forms, file inputs, integration with non-React code

Basic Controlled Input

function ControlledForm() {
  const [email, setEmail] = useState('');
  const [isValid, setIsValid] = useState(true);

  const handleChange = (e) => {
    const value = e.target.value;
    setEmail(value);
    setIsValid(value.includes('@')); // Real-time validation
  };

  return (
    <form>
      <input
        type="email"
        value={email}
        onChange={handleChange}
        className={isValid ? '' : 'error'}
      />
      {!isValid && <span>Please enter a valid email</span>}
    </form>
  );
}

Multiple Inputs with Single Handler

function MultiInputForm() {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    password: ''
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };

  return (
    <form>
      <input name="username" value={formData.username} onChange={handleChange} />
      <input name="email" value={formData.email} onChange={handleChange} />
      <input name="password" type="password" value={formData.password} onChange={handleChange} />
    </form>
  );
}

Common Patterns


4. Effects (Side Effects)

The useEffect hook lets you synchronize a component with an external system (fetching data, subscriptions).

import { useState, useEffect } from 'react';

function DataFetcher() {
  const [data, setData] = useState(null);

  useEffect(() => {
    // This runs after the component renders
    fetch('https://api.example.com/data')
      .then(res => res.json())
      .then(data => setData(data));
      
    // Optional cleanup function
    return () => console.log('Cleanup if needed');
  }, []); // Empty array [] means run only once on mount

  if (!data) return <div>Loading...</div>;
  return <div>{data.message}</div>;
}

5. Rendering Lists

Use the .map() array method to transform data into lists of elements.

function ShoppingList() {
  const products = [
    { title: 'Cabbage', id: 1 },
    { title: 'Garlic', id: 2 },
    { title: 'Apple', id: 3 },
  ];

  return (
    <ul>
      {products.map(product => (
        <li key={product.id}>
          {product.title}
        </li>
      ))}
    </ul>
  );
}


6. Advanced Hooks

useReducer - Complex State Management

When state updates depend on previous state or involve complex logic, useReducer is more maintainable than multiple useState calls.

import { useReducer } from 'react';

// Reducer function: (currentState, action) => newState
function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: 0 };
    default:
      return state;
  }
}

function Counter() {
  // useReducer returns [state, dispatch]
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  );
}

When to use useReducer:


useContext - Share Data Without Prop Drilling

Context lets you pass data through the component tree without manually passing props at every level.

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

// 1. Create a context
const ThemeContext = createContext();

// 2. Provider component wraps the app
function App() {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value=>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// 3. Consume context in any child component
function Toolbar() {
  return <div><ThemeButton /></div>;
}

function ThemeButton() {
  // Access context value without props
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <button
      onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
      style=
    >
      Current theme: {theme}
    </button>
  );
}

Context pattern for auth:

// contexts/AuthContext.jsx
import { createContext, useContext, useState } from 'react';

const AuthContext = createContext();

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  const login = (userData) => setUser(userData);
  const logout = () => setUser(null);

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

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

Context with LocalStorage Persistence:

Persist context state across page reloads using localStorage.

// contexts/ThemeContext.jsx
import { createContext, useContext, useState, useEffect } from 'react';

const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  // Initialize theme from localStorage or default to 'light'
  const [isDarkMode, setIsDarkMode] = useState(() => {
    const saved = localStorage.getItem('theme');
    return saved === 'dark'; // Returns true if dark, false otherwise
  });

  // Persist theme to localStorage whenever it changes
  useEffect(() => {
    localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');
  }, [isDarkMode]);

  const toggleTheme = () => {
    setIsDarkMode(prev => !prev);
  };

  const value = {
    isDarkMode,
    toggleTheme,
    theme: isDarkMode ? 'dark' : 'light'
  };

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

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

// Usage in components:
function App() {
  return (
    <ThemeProvider>
      <Header />
      <Content />
    </ThemeProvider>
  );
}

function Header() {
  const { isDarkMode, toggleTheme } = useTheme();

  return (
    <header style=>
      <button onClick={toggleTheme}>
        Toggle to {isDarkMode ? 'Light' : 'Dark'} Mode
      </button>
    </header>
  );
}

Why This Pattern Works:


useMemo - Memoize Expensive Calculations

useMemo caches the result of a calculation between re-renders, only recalculating when dependencies change.

import { useState, useMemo } from 'react';

function ExpensiveComponent({ data }) {
  const [filterTerm, setFilterTerm] = useState('');

  // Without useMemo, this runs on every render
  // With useMemo, only runs when data or filterTerm change
  const filteredData = useMemo(() => {
    console.log('Filtering data...');
    return data.filter(item =>
      item.name.toLowerCase().includes(filterTerm.toLowerCase())
    );
  }, [data, filterTerm]); // Dependency array

  return (
    <div>
      <input
        value={filterTerm}
        onChange={(e) => setFilterTerm(e.target.value)}
        placeholder="Filter..."
      />
      <ul>
        {filteredData.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

When to use useMemo:

Warning: Don’t overuse! Only use when profiling shows a performance issue.


useCallback - Memoize Function References

useCallback returns a memoized version of a callback function, preventing unnecessary re-renders of child components.

import { useState, useCallback } from 'react';

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [other, setOther] = useState(0);

  // Without useCallback, this function is recreated on every render
  // With useCallback, same function reference is returned unless count changes
  const handleClick = useCallback(() => {
    console.log('Count is:', count);
  }, [count]); // Only recreate if count changes

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setOther(other + 1)}>Other: {other}</button>

      {/* ChildComponent won't re-render when 'other' changes */}
      <ChildComponent onClick={handleClick} />
    </div>
  );
}

// React.memo prevents re-renders when props haven't changed
const ChildComponent = React.memo(({ onClick }) => {
  console.log('ChildComponent rendered');
  return <button onClick={onClick}>Click me</button>;
});

When to use useCallback:


7. Custom Hooks

Custom hooks let you extract component logic into reusable functions. Convention: name starts with “use”.

Example 1: useLocalStorage Hook

import { useState, useEffect } from 'react';

// Custom hook for syncing state with localStorage
function useLocalStorage(key, initialValue) {
  // Get initial value from localStorage or use initialValue
  const [value, setValue] = useState(() => {
    const item = window.localStorage.getItem(key);
    return item ? JSON.parse(item) : initialValue;
  });

  // Update localStorage when value changes
  useEffect(() => {
    window.localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

// Usage
function UserSettings() {
  const [name, setName] = useLocalStorage('username', '');
  const [theme, setTheme] = useLocalStorage('theme', 'light');

  return (
    <div>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Your name"
      />
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle Theme
      </button>
    </div>
  );
}

Example 2: useFetch Hook

import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let isMounted = true; // Prevent setting state after unmount

    setLoading(true);
    fetch(url)
      .then(res => {
        if (!res.ok) throw new Error('Network response was not ok');
        return res.json();
      })
      .then(data => {
        if (isMounted) {
          setData(data);
          setLoading(false);
        }
      })
      .catch(error => {
        if (isMounted) {
          setError(error);
          setLoading(false);
        }
      });

    return () => {
      isMounted = false; // Cleanup
    };
  }, [url]);

  return { data, loading, error };
}

// Usage
function UserProfile({ userId }) {
  const { data, loading, error } = useFetch(`/api/users/${userId}`);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

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

Example 3: useToggle Hook

import { useState, useCallback } from 'react';

function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);

  const toggle = useCallback(() => {
    setValue(v => !v);
  }, []);

  return [value, toggle];
}

// Usage
function Modal() {
  const [isOpen, toggleOpen] = useToggle(false);

  return (
    <div>
      <button onClick={toggleOpen}>Open Modal</button>
      {isOpen && (
        <div className="modal">
          <p>Modal Content</p>
          <button onClick={toggleOpen}>Close</button>
        </div>
      )}
    </div>
  );
}

8. Component Composition Patterns

Render Props Pattern

Pass a function as a prop that returns JSX.

// Generic mouse tracker
function MouseTracker({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const handleMouseMove = (event) => {
    setPosition({ x: event.clientX, y: event.clientY });
  };

  return (
    <div onMouseMove={handleMouseMove} style=>
      {render(position)}
    </div>
  );
}

// Usage - inject custom rendering
function App() {
  return (
    <MouseTracker render={({ x, y }) => (
      <h1>Mouse position: {x}, {y}</h1>
    )} />
  );
}

Compound Components Pattern

Components that work together to form a complete UI.

// Tabs component with compound pattern
function Tabs({ children }) {
  const [activeIndex, setActiveIndex] = useState(0);

  return (
    <div className="tabs">
      {React.Children.map(children, (child, index) =>
        React.cloneElement(child, {
          isActive: index === activeIndex,
          onClick: () => setActiveIndex(index)
        })
      )}
    </div>
  );
}

function Tab({ label, children, isActive, onClick }) {
  return (
    <div onClick={onClick} className={isActive ? 'active' : ''}>
      <div className="tab-header">{label}</div>
      {isActive && <div className="tab-content">{children}</div>}
    </div>
  );
}

// Usage
<Tabs>
  <Tab label="Profile">Profile content</Tab>
  <Tab label="Settings">Settings content</Tab>
  <Tab label="Notifications">Notifications content</Tab>
</Tabs>

9. Performance Optimization

React.memo - Prevent Unnecessary Re-renders

import { memo } from 'react';

// Without memo: re-renders every time parent re-renders
// With memo: only re-renders if props change
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
  console.log('Rendering ExpensiveComponent');
  // Heavy rendering logic...
  return <div>{data.map(/* render items */)}</div>;
});

// Custom comparison function (optional)
const MemoizedComponent = memo(
  ({ user }) => <div>{user.name}</div>,
  (prevProps, nextProps) => prevProps.user.id === nextProps.user.id
);

Lazy Loading Components

import { lazy, Suspense } from 'react';

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

function App() {
  return (
    <div>
      <h1>My App</h1>

      {/* Suspense shows fallback while component loads */}
      <Suspense fallback={<div>Loading chart...</div>}>
        <HeavyChart />
      </Suspense>

      <Suspense fallback={<div>Loading dashboard...</div>}>
        <Dashboard />
      </Suspense>
    </div>
  );
}

Avoid Inline Functions and Objects

// BAD: Creates new function on every render
function BadExample() {
  return <ChildComponent onClick={() => console.log('clicked')} />;
}

// GOOD: Function reference is stable
function GoodExample() {
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);

  return <ChildComponent onClick={handleClick} />;
}

// BAD: Creates new object on every render
<ChildComponent style= />

// GOOD: Define outside component or use useMemo
const style = { color: 'red' };
<ChildComponent style={style} />

10. Error Boundaries

Error boundaries catch JavaScript errors in child components and display fallback UI.

import { Component } from 'react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    // Update state so next render shows fallback UI
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    // Log error to error reporting service
    console.error('Error caught by boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-fallback">
          <h2>Something went wrong</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false })}>
            Try again
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

// Usage
function App() {
  return (
    <ErrorBoundary>
      <ComponentThatMightError />
    </ErrorBoundary>
  );
}

Note: Error boundaries don’t catch:


11. Common React Mistakes

1. Mutating State Directly

// WRONG: Mutating state array
const [items, setItems] = useState([1, 2, 3]);
items.push(4); // DON'T DO THIS!
setItems(items);

// CORRECT: Create new array
setItems([...items, 4]); // or items.concat(4)

2. Stale Closure in useEffect

// WRONG: count is stale (always 0)
useEffect(() => {
  const interval = setInterval(() => {
    setCount(count + 1); // count is captured as 0
  }, 1000);
  return () => clearInterval(interval);
}, []); // Empty deps - count never updates!

// CORRECT: Use functional update
useEffect(() => {
  const interval = setInterval(() => {
    setCount(c => c + 1); // Gets current count
  }, 1000);
  return () => clearInterval(interval);
}, []);

3. Missing Keys in Lists

// WRONG: Using index as key
{items.map((item, index) => (
  <div key={index}>{item.name}</div>
))}

// CORRECT: Use stable unique identifier
{items.map(item => (
  <div key={item.id}>{item.name}</div>
))}

4. Not Cleaning Up Effects

// WRONG: Memory leak - no cleanup
useEffect(() => {
  const subscription = props.source.subscribe();
}, [props.source]);

// CORRECT: Return cleanup function
useEffect(() => {
  const subscription = props.source.subscribe();
  return () => subscription.unsubscribe();
}, [props.source]);

5. Conditional Hooks

// WRONG: Hooks must be called unconditionally
if (condition) {
  useEffect(() => { /* ... */ });
}

// CORRECT: Condition inside hook
useEffect(() => {
  if (condition) {
    // Do something
  }
}, [condition]);

6. Forgetting Dependency Arrays

// WRONG: Runs on every render
useEffect(() => {
  fetchData();
}); // No dependency array!

// CORRECT: Specify dependencies
useEffect(() => {
  fetchData();
}, [someValue]); // Or [] for run-once

12. Best Practices Summary

State Management

Performance

Component Design

Effects


See Also