Home / Notebooks / Frontend Development
Frontend Development
intermediate

ReactJS Essentials

Complete guide to building modern web applications with React - components, hooks, state management, and best practices

April 21, 2026
Updated regularly

ReactJS Essentials

React is a powerful JavaScript library for building user interfaces, developed and maintained by Meta. It enables developers to create fast, interactive web applications through a component-based architecture.

What is React?

React is a declarative, efficient, and flexible JavaScript library for building user interfaces. It lets you compose complex UIs from small, isolated pieces of code called components.

Key Features:

  • Component-based architecture
  • Virtual DOM for performance
  • Unidirectional data flow
  • JSX syntax
  • Rich ecosystem and community
  • Server-side rendering support
  • Why Use React?

  • Reusable components reduce code duplication
  • Virtual DOM ensures optimal performance
  • Large community and extensive libraries
  • Strong developer tools
  • Mobile development with React Native
  • Backed by Meta (Facebook)
  • Getting Started

    Installation

    Create a New React App:

    # Using Create React App (CRA)
    npx create-react-app my-app
    cd my-app
    npm start
    
    # Using Vite (Recommended - Faster)
    npm create vite@latest my-app -- --template react
    cd my-app
    npm install
    npm run dev
    
    # Using Next.js (Full-stack framework)
    npx create-next-app@latest my-app
    cd my-app
    npm run dev
    

    Project Structure

    my-app/
    ├── node_modules/
    ├── public/
    │   ├── index.html
    │   └── favicon.ico
    ├── src/
    │   ├── components/
    │   ├── App.js
    │   ├── App.css
    │   ├── index.js
    │   └── index.css
    ├── package.json
    └── README.md
    

    Core Concepts

    JSX (JavaScript XML)

    JSX is a syntax extension that looks similar to HTML but works within JavaScript.

    // Basic JSX
    const element = <h1>Hello, React!</h1>;
    
    // JSX with expressions
    const name = "John";
    const element = <h1>Hello, {name}!</h1>;
    
    // JSX with attributes
    const element = <img src={user.avatarUrl} alt={user.name} />;
    
    // JSX with children
    const element = (
      <div>
        <h1>Welcome</h1>
        <p>This is a paragraph</p>
      </div>
    );
    
    // JSX must return a single parent element
    // Good
    const element = (
      <div>
        <h1>Title</h1>
        <p>Content</p>
      </div>
    );
    
    // Or use Fragment
    const element = (
      <>
        <h1>Title</h1>
        <p>Content</p>
      </>
    );
    
    // Conditional rendering
    const element = (
      <div>
        {isLoggedIn ? <LogoutButton /> : <LoginButton />}
      </div>
    );
    
    // List rendering
    const numbers = [1, 2, 3, 4, 5];
    const listItems = numbers.map((number) => (
      <li key={number}>{number}</li>
    ));
    

    JSX Rules:

  • Must return a single parent element
  • Use className instead of class
  • Use htmlFor instead of for
  • Close all tags (including self-closing)
  • Use camelCase for attributes
  • Components

    Components are the building blocks of React applications.

    Function Components (Modern Approach):

    // Simple function component
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    
    // Arrow function component
    const Welcome = (props) => {
      return <h1>Hello, {props.name}</h1>;
    };
    
    // Component with destructured props
    const Welcome = ({ name, age }) => {
      return (
        <div>
          <h1>Hello, {name}</h1>
          <p>Age: {age}</p>
        </div>
      );
    };
    
    // Using the component
    <Welcome name="John" age={30} />
    

    Class Components (Legacy):

    import React, { Component } from 'react';
    
    class Welcome extends Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }
    

    Props (Properties)

    Props are arguments passed to components, similar to function parameters.

    // Parent component
    function App() {
      return (
        <div>
          <UserCard 
            name="John Doe"
            email="john@example.com"
            age={30}
            isActive={true}
          />
        </div>
      );
    }
    
    // Child component
    function UserCard({ name, email, age, isActive }) {
      return (
        <div className="card">
          <h2>{name}</h2>
          <p>{email}</p>
          <p>Age: {age}</p>
          {isActive && <span className="badge">Active</span>}
        </div>
      );
    }
    
    // Props with default values
    function Button({ text = "Click me", variant = "primary" }) {
      return <button className={`btn btn-${variant}`}>{text}</button>;
    }
    
    // Props validation with PropTypes
    import PropTypes from 'prop-types';
    
    UserCard.propTypes = {
      name: PropTypes.string.isRequired,
      email: PropTypes.string.isRequired,
      age: PropTypes.number,
      isActive: PropTypes.bool
    };
    
    // Children prop
    function Card({ children, title }) {
      return (
        <div className="card">
          <h3>{title}</h3>
          <div className="card-body">
            {children}
          </div>
        </div>
      );
    }
    
    // Usage
    <Card title="My Card">
      <p>This is the content</p>
      <button>Click me</button>
    </Card>
    

    State

    State is data that changes over time within a component.

    import { useState } from 'react';
    
    // Basic state
    function Counter() {
      const [count, setCount] = useState(0);
      
      return (
        <div>
          <p>Count: {count}</p>
          <button onClick={() => setCount(count + 1)}>Increment</button>
          <button onClick={() => setCount(count - 1)}>Decrement</button>
          <button onClick={() => setCount(0)}>Reset</button>
        </div>
      );
    }
    
    // State with objects
    function UserForm() {
      const [user, setUser] = useState({
        name: '',
        email: '',
        age: 0
      });
      
      const handleChange = (e) => {
        setUser({
          ...user,
          [e.target.name]: e.target.value
        });
      };
      
      return (
        <form>
          <input 
            name="name" 
            value={user.name} 
            onChange={handleChange}
            placeholder="Name"
          />
          <input 
            name="email" 
            value={user.email} 
            onChange={handleChange}
            placeholder="Email"
          />
          <input 
            name="age" 
            type="number"
            value={user.age} 
            onChange={handleChange}
            placeholder="Age"
          />
        </form>
      );
    }
    
    // State with arrays
    function TodoList() {
      const [todos, setTodos] = useState([]);
      const [input, setInput] = useState('');
      
      const addTodo = () => {
        setTodos([...todos, { id: Date.now(), text: input }]);
        setInput('');
      };
      
      const removeTodo = (id) => {
        setTodos(todos.filter(todo => todo.id !== id));
      };
      
      return (
        <div>
          <input 
            value={input} 
            onChange={(e) => setInput(e.target.value)}
          />
          <button onClick={addTodo}>Add</button>
          <ul>
            {todos.map(todo => (
              <li key={todo.id}>
                {todo.text}
                <button onClick={() => removeTodo(todo.id)}>Delete</button>
              </li>
            ))}
          </ul>
        </div>
      );
    }
    
    // State with previous value
    function Counter() {
      const [count, setCount] = useState(0);
      
      // Correct way when new state depends on previous state
      const increment = () => {
        setCount(prevCount => prevCount + 1);
      };
      
      return <button onClick={increment}>Count: {count}</button>;
    }
    

    React Hooks

    Hooks let you use state and other React features in function components.

    useState

    import { useState } from 'react';
    
    function Example() {
      // Declare state variable
      const [count, setCount] = useState(0);
      
      // Multiple state variables
      const [name, setName] = useState('');
      const [isActive, setIsActive] = useState(false);
      
      return (
        <div>
          <p>{count}</p>
          <button onClick={() => setCount(count + 1)}>Increment</button>
        </div>
      );
    }
    

    useEffect

    import { useState, useEffect } from 'react';
    
    // Run on every render
    function Example1() {
      useEffect(() => {
        console.log('Component rendered');
      });
    }
    
    // Run only once (on mount)
    function Example2() {
      useEffect(() => {
        console.log('Component mounted');
      }, []);
    }
    
    // Run when dependencies change
    function Example3() {
      const [count, setCount] = useState(0);
      
      useEffect(() => {
        console.log('Count changed:', count);
      }, [count]);
      
      return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
    }
    
    // Cleanup function
    function Example4() {
      useEffect(() => {
        const timer = setInterval(() => {
          console.log('Tick');
        }, 1000);
        
        // Cleanup
        return () => {
          clearInterval(timer);
        };
      }, []);
    }
    
    // Fetching data
    function UserProfile({ userId }) {
      const [user, setUser] = useState(null);
      const [loading, setLoading] = useState(true);
      const [error, setError] = useState(null);
      
      useEffect(() => {
        const fetchUser = async () => {
          try {
            setLoading(true);
            const response = await fetch(`/api/users/${userId}`);
            const data = await response.json();
            setUser(data);
          } catch (err) {
            setError(err.message);
          } finally {
            setLoading(false);
          }
        };
        
        fetchUser();
      }, [userId]);
      
      if (loading) return <div>Loading...</div>;
      if (error) return <div>Error: {error}</div>;
      
      return <div>{user.name}</div>;
    }
    

    useContext

    import { createContext, useContext, useState } from 'react';
    
    // Create context
    const ThemeContext = createContext();
    
    // Provider component
    function ThemeProvider({ children }) {
      const [theme, setTheme] = useState('light');
      
      const toggleTheme = () => {
        setTheme(theme === 'light' ? 'dark' : 'light');
      };
      
      return (
        <ThemeContext.Provider value={{ theme, toggleTheme }}>
          {children}
        </ThemeContext.Provider>
      );
    }
    
    // Consumer component
    function ThemedButton() {
      const { theme, toggleTheme } = useContext(ThemeContext);
      
      return (
        <button 
          className={`btn-${theme}`}
          onClick={toggleTheme}
        >
          Current theme: {theme}
        </button>
      );
    }
    
    // App
    function App() {
      return (
        <ThemeProvider>
          <ThemedButton />
        </ThemeProvider>
      );
    }
    

    useRef

    import { useRef, useEffect } from 'react';
    
    // Accessing DOM elements
    function TextInput() {
      const inputRef = useRef(null);
      
      const focusInput = () => {
        inputRef.current.focus();
      };
      
      return (
        <div>
          <input ref={inputRef} />
          <button onClick={focusInput}>Focus Input</button>
        </div>
      );
    }
    
    // Storing mutable values
    function Timer() {
      const [count, setCount] = useState(0);
      const intervalRef = useRef(null);
      
      const startTimer = () => {
        intervalRef.current = setInterval(() => {
          setCount(c => c + 1);
        }, 1000);
      };
      
      const stopTimer = () => {
        clearInterval(intervalRef.current);
      };
      
      return (
        <div>
          <p>Count: {count}</p>
          <button onClick={startTimer}>Start</button>
          <button onClick={stopTimer}>Stop</button>
        </div>
      );
    }
    
    // Previous value tracking
    function usePrevious(value) {
      const ref = useRef();
      
      useEffect(() => {
        ref.current = value;
      }, [value]);
      
      return ref.current;
    }
    

    useReducer

    import { useReducer } from 'react';
    
    // Reducer function
    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:
          throw new Error('Unknown action type');
      }
    }
    
    function Counter() {
      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>
      );
    }
    
    // Complex state management
    function todoReducer(state, action) {
      switch (action.type) {
        case 'add':
          return [...state, { id: Date.now(), text: action.payload, done: false }];
        case 'toggle':
          return state.map(todo =>
            todo.id === action.payload
              ? { ...todo, done: !todo.done }
              : todo
          );
        case 'delete':
          return state.filter(todo => todo.id !== action.payload);
        default:
          return state;
      }
    }
    
    function TodoApp() {
      const [todos, dispatch] = useReducer(todoReducer, []);
      const [input, setInput] = useState('');
      
      const handleSubmit = (e) => {
        e.preventDefault();
        dispatch({ type: 'add', payload: input });
        setInput('');
      };
      
      return (
        <div>
          <form onSubmit={handleSubmit}>
            <input value={input} onChange={(e) => setInput(e.target.value)} />
            <button type="submit">Add</button>
          </form>
          <ul>
            {todos.map(todo => (
              <li key={todo.id}>
                <input
                  type="checkbox"
                  checked={todo.done}
                  onChange={() => dispatch({ type: 'toggle', payload: todo.id })}
                />
                {todo.text}
                <button onClick={() => dispatch({ type: 'delete', payload: todo.id })}>
                  Delete
                </button>
              </li>
            ))}
          </ul>
        </div>
      );
    }
    

    useMemo

    import { useMemo, useState } from 'react';
    
    function ExpensiveComponent({ items }) {
      const [filter, setFilter] = useState('');
      
      // Memoize expensive calculation
      const filteredItems = useMemo(() => {
        console.log('Filtering items...');
        return items.filter(item =>
          item.name.toLowerCase().includes(filter.toLowerCase())
        );
      }, [items, filter]);
      
      return (
        <div>
          <input
            value={filter}
            onChange={(e) => setFilter(e.target.value)}
            placeholder="Filter items"
          />
          <ul>
            {filteredItems.map(item => (
              <li key={item.id}>{item.name}</li>
            ))}
          </ul>
        </div>
      );
    }
    

    useCallback

    import { useCallback, useState, memo } from 'react';
    
    // Child component
    const Button = memo(({ onClick, children }) => {
      console.log('Button rendered');
      return <button onClick={onClick}>{children}</button>;
    });
    
    // Parent component
    function Parent() {
      const [count, setCount] = useState(0);
      const [other, setOther] = useState(0);
      
      // Without useCallback, this function is recreated on every render
      const handleClick = useCallback(() => {
        setCount(c => c + 1);
      }, []);
      
      return (
        <div>
          <p>Count: {count}</p>
          <p>Other: {other}</p>
          <Button onClick={handleClick}>Increment Count</Button>
          <button onClick={() => setOther(other + 1)}>Increment Other</button>
        </div>
      );
    }
    

    Custom Hooks

    // useLocalStorage hook
    function useLocalStorage(key, initialValue) {
      const [storedValue, setStoredValue] = useState(() => {
        try {
          const item = window.localStorage.getItem(key);
          return item ? JSON.parse(item) : initialValue;
        } catch (error) {
          return initialValue;
        }
      });
      
      const setValue = (value) => {
        try {
          setStoredValue(value);
          window.localStorage.setItem(key, JSON.stringify(value));
        } catch (error) {
          console.error(error);
        }
      };
      
      return [storedValue, setValue];
    }
    
    // Usage
    function App() {
      const [name, setName] = useLocalStorage('name', '');
      
      return (
        <input
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
      );
    }
    
    // useFetch hook
    function useFetch(url) {
      const [data, setData] = useState(null);
      const [loading, setLoading] = useState(true);
      const [error, setError] = useState(null);
      
      useEffect(() => {
        const fetchData = async () => {
          try {
            setLoading(true);
            const response = await fetch(url);
            const json = await response.json();
            setData(json);
          } catch (err) {
            setError(err);
          } finally {
            setLoading(false);
          }
        };
        
        fetchData();
      }, [url]);
      
      return { data, loading, error };
    }
    
    // Usage
    function UserList() {
      const { data, loading, error } = useFetch('/api/users');
      
      if (loading) return <div>Loading...</div>;
      if (error) return <div>Error: {error.message}</div>;
      
      return (
        <ul>
          {data.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      );
    }
    

    Event Handling

    // Basic event handling
    function Button() {
      const handleClick = () => {
        console.log('Button clicked');
      };
      
      return <button onClick={handleClick}>Click me</button>;
    }
    
    // Event with parameter
    function Button() {
      const handleClick = (id) => {
        console.log('Clicked item:', id);
      };
      
      return <button onClick={() => handleClick(123)}>Click me</button>;
    }
    
    // Event object
    function Form() {
      const handleSubmit = (e) => {
        e.preventDefault();
        console.log('Form submitted');
      };
      
      return <form onSubmit={handleSubmit}>...</form>;
    }
    
    // Common events
    function Example() {
      return (
        <div>
          <button onClick={() => {}}>Click</button>
          <input onChange={(e) => {}} />
          <form onSubmit={(e) => {}}>...</form>
          <div onMouseEnter={() => {}}>Hover me</div>
          <input onFocus={() => {}} onBlur={() => {}} />
          <input onKeyDown={(e) => {}} onKeyUp={(e) => {}} />
        </div>
      );
    }
    

    Forms

    // Controlled components
    function LoginForm() {
      const [formData, setFormData] = useState({
        email: '',
        password: ''
      });
      
      const handleChange = (e) => {
        setFormData({
          ...formData,
          [e.target.name]: e.target.value
        });
      };
      
      const handleSubmit = (e) => {
        e.preventDefault();
        console.log('Submitted:', formData);
      };
      
      return (
        <form onSubmit={handleSubmit}>
          <input
            type="email"
            name="email"
            value={formData.email}
            onChange={handleChange}
            placeholder="Email"
          />
          <input
            type="password"
            name="password"
            value={formData.password}
            onChange={handleChange}
            placeholder="Password"
          />
          <button type="submit">Login</button>
        </form>
      );
    }
    
    // Form validation
    function SignupForm() {
      const [formData, setFormData] = useState({
        username: '',
        email: '',
        password: ''
      });
      const [errors, setErrors] = useState({});
      
      const validate = () => {
        const newErrors = {};
        
        if (!formData.username) {
          newErrors.username = 'Username 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.password) {
          newErrors.password = 'Password is required';
        } else if (formData.password.length < 6) {
          newErrors.password = 'Password must be at least 6 characters';
        }
        
        return newErrors;
      };
      
      const handleSubmit = (e) => {
        e.preventDefault();
        const newErrors = validate();
        
        if (Object.keys(newErrors).length === 0) {
          console.log('Form is valid', formData);
        } else {
          setErrors(newErrors);
        }
      };
      
      return (
        <form onSubmit={handleSubmit}>
          <div>
            <input
              name="username"
              value={formData.username}
              onChange={(e) => setFormData({...formData, username: e.target.value})}
            />
            {errors.username && <span className="error">{errors.username}</span>}
          </div>
          {/* Similar for other fields */}
          <button type="submit">Sign Up</button>
        </form>
      );
    }
    

    Conditional Rendering

    // If statement
    function Greeting({ isLoggedIn }) {
      if (isLoggedIn) {
        return <h1>Welcome back!</h1>;
      }
      return <h1>Please sign in</h1>;
    }
    
    // Ternary operator
    function Greeting({ isLoggedIn }) {
      return (
        <div>
          {isLoggedIn ? <h1>Welcome back!</h1> : <h1>Please sign in</h1>}
        </div>
      );
    }
    
    // Logical AND
    function Mailbox({ unreadMessages }) {
      return (
        <div>
          <h1>Hello!</h1>
          {unreadMessages.length > 0 && (
            <h2>You have {unreadMessages.length} unread messages.</h2>
          )}
        </div>
      );
    }
    
    // Switch case
    function Status({ status }) {
      switch (status) {
        case 'loading':
          return <Spinner />;
        case 'error':
          return <ErrorMessage />;
        case 'success':
          return <SuccessMessage />;
        default:
          return null;
      }
    }
    

    Lists and Keys

    // Basic list
    function NumberList({ numbers }) {
      return (
        <ul>
          {numbers.map((number) => (
            <li key={number}>{number}</li>
          ))}
        </ul>
      );
    }
    
    // List with objects
    function UserList({ users }) {
      return (
        <ul>
          {users.map((user) => (
            <li key={user.id}>
              <h3>{user.name}</h3>
              <p>{user.email}</p>
            </li>
          ))}
        </ul>
      );
    }
    
    // List with index (only when items don't have stable IDs)
    function TodoList({ todos }) {
      return (
        <ul>
          {todos.map((todo, index) => (
            <li key={index}>{todo}</li>
          ))}
        </ul>
      );
    }
    

    Styling

    // Inline styles
    function StyledComponent() {
      const style = {
        color: 'blue',
        fontSize: '20px',
        backgroundColor: 'lightgray'
      };
      
      return <div style={style}>Styled text</div>;
    }
    
    // CSS classes
    import './Button.css';
    
    function Button({ variant = 'primary' }) {
      return <button className={`btn btn-${variant}`}>Click me</button>;
    }
    
    // CSS Modules
    import styles from './Button.module.css';
    
    function Button() {
      return <button className={styles.button}>Click me</button>;
    }
    
    // Conditional classes
    function Button({ isActive, isPrimary }) {
      const className = `btn ${isActive ? 'active' : ''} ${isPrimary ? 'primary' : 'secondary'}`;
      return <button className={className}>Click me</button>;
    }
    
    // Using classnames library
    import classNames from 'classnames';
    
    function Button({ isActive, isPrimary }) {
      return (
        <button className={classNames('btn', {
          'active': isActive,
          'primary': isPrimary,
          'secondary': !isPrimary
        })}>
          Click me
        </button>
      );
    }
    

    React Router

    import { BrowserRouter, Routes, Route, Link, useParams, useNavigate } from 'react-router-dom';
    
    // Basic routing
    function App() {
      return (
        <BrowserRouter>
          <nav>
            <Link to="/">Home</Link>
            <Link to="/about">About</Link>
            <Link to="/contact">Contact</Link>
          </nav>
          
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/about" element={<About />} />
            <Route path="/contact" element={<Contact />} />
            <Route path="*" element={<NotFound />} />
          </Routes>
        </BrowserRouter>
      );
    }
    
    // Dynamic routes
    function App() {
      return (
        <Routes>
          <Route path="/users/:id" element={<UserProfile />} />
          <Route path="/products/:category/:id" element={<Product />} />
        </Routes>
      );
    }
    
    function UserProfile() {
      const { id } = useParams();
      return <div>User ID: {id}</div>;
    }
    
    // Programmatic navigation
    function LoginForm() {
      const navigate = useNavigate();
      
      const handleSubmit = async (e) => {
        e.preventDefault();
        await login();
        navigate('/dashboard');
      };
      
      return <form onSubmit={handleSubmit}>...</form>;
    }
    
    // Nested routes
    function App() {
      return (
        <Routes>
          <Route path="/dashboard" element={<Dashboard />}>
            <Route path="profile" element={<Profile />} />
            <Route path="settings" element={<Settings />} />
          </Route>
        </Routes>
      );
    }
    
    // Protected routes
    function ProtectedRoute({ children }) {
      const isAuthenticated = useAuth();
      return isAuthenticated ? children : <Navigate to="/login" />;
    }
    
    function App() {
      return (
        <Routes>
          <Route path="/login" element={<Login />} />
          <Route
            path="/dashboard"
            element={
              <ProtectedRoute>
                <Dashboard />
              </ProtectedRoute>
            }
          />
        </Routes>
      );
    }
    

    Performance Optimization

    // React.memo
    const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
      console.log('Rendering ExpensiveComponent');
      return <div>{data}</div>;
    });
    
    // useMemo for expensive calculations
    function SearchResults({ items, query }) {
      const filteredItems = useMemo(() => {
        return items.filter(item =>
          item.name.toLowerCase().includes(query.toLowerCase())
        );
      }, [items, query]);
      
      return <List items={filteredItems} />;
    }
    
    // useCallback for stable function references
    function Parent() {
      const [count, setCount] = useState(0);
      
      const handleClick = useCallback(() => {
        setCount(c => c + 1);
      }, []);
      
      return <Child onClick={handleClick} />;
    }
    
    // Code splitting
    import { lazy, Suspense } from 'react';
    
    const HeavyComponent = lazy(() => import('./HeavyComponent'));
    
    function App() {
      return (
        <Suspense fallback={<div>Loading...</div>}>
          <HeavyComponent />
        </Suspense>
      );
    }
    
    // Virtualization for long lists
    import { FixedSizeList } from 'react-window';
    
    function VirtualizedList({ items }) {
      return (
        <FixedSizeList
          height={500}
          itemCount={items.length}
          itemSize={50}
          width="100%"
        >
          {({ index, style }) => (
            <div style={style}>{items[index].name}</div>
          )}
        </FixedSizeList>
      );
    }
    

    Best Practices

    Component Organization

    // Good: Single responsibility
    function UserCard({ user }) {
      return (
        <div className="card">
          <UserAvatar src={user.avatar} />
          <UserInfo name={user.name} email={user.email} />
          <UserActions userId={user.id} />
        </div>
      );
    }
    
    // Bad: Too many responsibilities
    function UserCard({ user }) {
      // Mixing UI, logic, and data fetching
    }
    

    State Management

    // Good: Lift state up when needed
    function Parent() {
      const [sharedData, setSharedData] = useState(null);
      
      return (
        <>
          <ChildA data={sharedData} />
          <ChildB data={sharedData} onUpdate={setSharedData} />
        </>
      );
    }
    
    // Good: Keep state local when possible
    function Component() {
      const [localState, setLocalState] = useState('');
      // Only this component needs this state
    }
    

    Error Boundaries

    class ErrorBoundary extends React.Component {
      constructor(props) {
        super(props);
        this.state = { hasError: false };
      }
      
      static getDerivedStateFromError(error) {
        return { hasError: true };
      }
      
      componentDidCatch(error, errorInfo) {
        console.error('Error:', error, errorInfo);
      }
      
      render() {
        if (this.state.hasError) {
          return <h1>Something went wrong.</h1>;
        }
        
        return this.props.children;
      }
    }
    
    // Usage
    <ErrorBoundary>
      <MyComponent />
    </ErrorBoundary>
    

    Common Patterns

    Container/Presentation Pattern

    // Container (logic)
    function UserListContainer() {
      const { data, loading, error } = useFetch('/api/users');
      
      if (loading) return <Spinner />;
      if (error) return <Error message={error} />;
      
      return <UserListPresentation users={data} />;
    }
    
    // Presentation (UI)
    function UserListPresentation({ users }) {
      return (
        <ul>
          {users.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      );
    }
    

    Compound Components

    function Tabs({ children }) {
      const [activeTab, setActiveTab] = useState(0);
      
      return (
        <div className="tabs">
          {React.Children.map(children, (child, index) =>
            React.cloneElement(child, {
              isActive: index === activeTab,
              onClick: () => setActiveTab(index)
            })
          )}
        </div>
      );
    }
    
    function Tab({ title, children, isActive, onClick }) {
      return (
        <div className={isActive ? 'active' : ''} onClick={onClick}>
          <h3>{title}</h3>
          {isActive && <div>{children}</div>}
        </div>
      );
    }
    
    // Usage
    <Tabs>
      <Tab title="Tab 1">Content 1</Tab>
      <Tab title="Tab 2">Content 2</Tab>
      <Tab title="Tab 3">Content 3</Tab>
    </Tabs>
    

    Testing

    import { render, screen, fireEvent } from '@testing-library/react';
    import userEvent from '@testing-library/user-event';
    
    // Component
    function Counter() {
      const [count, setCount] = useState(0);
      
      return (
        <div>
          <p>Count: {count}</p>
          <button onClick={() => setCount(count + 1)}>Increment</button>
        </div>
      );
    }
    
    // Test
    test('increments counter', () => {
      render(<Counter />);
      
      const button = screen.getByText('Increment');
      const count = screen.getByText(/Count:/);
      
      expect(count).toHaveTextContent('Count: 0');
      
      fireEvent.click(button);
      
      expect(count).toHaveTextContent('Count: 1');
    });
    
    // Testing with user events
    test('form submission', async () => {
      render(<LoginForm />);
      
      await userEvent.type(screen.getByLabelText('Email'), 'test@example.com');
      await userEvent.type(screen.getByLabelText('Password'), 'password123');
      await userEvent.click(screen.getByText('Login'));
      
      expect(screen.getByText('Welcome')).toBeInTheDocument();
    });
    

    Key Takeaways

  • Component-Based - Build UIs from reusable components
  • Declarative - Describe what the UI should look like
  • Unidirectional Data Flow - Data flows from parent to child
  • Hooks - Use state and effects in function components
  • Virtual DOM - React efficiently updates the real DOM
  • JSX - Write HTML-like syntax in JavaScript
  • State Management - Keep state minimal and local when possible
  • Performance - Use memo, useMemo, and useCallback wisely
  • Additional Resources

    Official Documentation:

  • React Docs: https://react.dev
  • React Router: https://reactrouter.com
  • Popular Libraries:

  • State Management: Redux, Zustand, Jotai
  • Styling: Tailwind CSS, Styled Components, Emotion
  • Forms: React Hook Form, Formik
  • UI Components: Material-UI, Ant Design, Chakra UI
  • Learning Resources:

  • React Beta Docs
  • FreeCodeCamp React Course
  • React Patterns by Kent C. Dodds
  • Next Steps

  • Build a simple todo app
  • Learn React Router for multi-page apps
  • Explore state management with Context API
  • Try a UI component library
  • Learn about testing with React Testing Library
  • Build a full-stack app with Next.js
  • Topics

    ReactJavaScriptFrontendWeb DevelopmentComponentsHooks

    Found This Helpful?

    If you have questions or suggestions for improving these notes, I'd love to hear from you.