React.js

Complete Guide: Components, Hooks, State Management & Modern Patterns

Introduction to React

React is a JavaScript library for building user interfaces. Created by Facebook, it uses a component-based architecture and virtual DOM for efficient rendering.

Key Concepts

  • Component-Based: Build encapsulated components that manage their own state
  • Declarative: Describe what UI should look like for any given state
  • Virtual DOM: Efficient updates by comparing virtual representations
  • Unidirectional Data Flow: Data flows down from parent to child
JSX
// Creating a React app
// npx create-react-app my-app
// npx create-vite@latest my-app --template react

// Basic React component
import React from 'react';

function App() {
  return (
    <div className="app">
      <h1>Hello, React!</h1>
    </div>
  );
}

export default App;

Components

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

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

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

// Class Component (Legacy)
class Greeting extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}!</h1>;
  }
}

// Component Composition
function Card({ title, children }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-content">
        {children}
      </div>
    </div>
  );
}

// Usage
<Card title="Welcome">
  <p>This is the card content</p>
  <Button text="Learn More" />
</Card>

JSX

JSX
// JSX = JavaScript XML
// Gets compiled to React.createElement()

// Embedding expressions
const name = "Alice";
const element = <h1>Hello, {name}!</h1>;

// Attributes
<img src={imageUrl} alt="description" />
<div className="container">  {/* class → className */}
<label htmlFor="name">       {/* for → htmlFor */}

// Inline styles (object notation)
<div style={{ color: 'red', fontSize: '16px' }}>Styled</div>

// Conditional rendering
{isLoggedIn && <UserDashboard />}
{isLoggedIn ? <Dashboard /> : <Login />}

// Rendering lists
const items = ['Apple', 'Banana', 'Cherry'];

<ul>
  {items.map((item, index) => (
    <li key={index}>{item}</li>
  ))}
</ul>

// Better key - use unique ID
<ul>
  {users.map(user => (
    <li key={user.id}>{user.name}</li>
  ))}
</ul>

// Fragments - return multiple elements
return (
  <>
    <Header />
    <Main />
    <Footer />
  </>
);

// Or with key
return (
  <React.Fragment key={id}>
    <dt>{item.term}</dt>
    <dd>{item.description}</dd>
  </React.Fragment>
);

State & Props

JSX
import { useState } from 'react';

// Props - data passed from parent
function Welcome({ name, age }) {
  return <p>{name} is {age} years old</p>;
}

// Usage
<Welcome name="Alice" age={25} />

// Props with children
function Container({ children, className }) {
  return <div className={className}>{children}</div>;
}

// State - component's internal data
function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

// Multiple state variables
function Form() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [isSubmitting, setIsSubmitting] = useState(false);
  
  // ...
}

// State with objects
function UserForm() {
  const [user, setUser] = useState({
    name: '',
    email: '',
    age: 0
  });
  
  // IMPORTANT: Always create new object
  const updateName = (name) => {
    setUser(prev => ({ ...prev, name }));
  };
  
  // DON'T mutate directly
  // user.name = 'Alice';  // WRONG!
}

// State with arrays
function TodoList() {
  const [todos, setTodos] = useState([]);
  
  const addTodo = (text) => {
    setTodos(prev => [...prev, { id: Date.now(), text }]);
  };
  
  const removeTodo = (id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id));
  };
  
  const updateTodo = (id, newText) => {
    setTodos(prev => prev.map(todo => 
      todo.id === id ? { ...todo, text: newText } : todo
    ));
  };
}

React Hooks

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

// useState - manage state
const [value, setValue] = useState(initialValue);

// Lazy initialization (for expensive computations)
const [state, setState] = useState(() => {
  return expensiveComputation();
});

// useRef - persist value without re-render
function TextInput() {
  const inputRef = useRef(null);
  
  const focusInput = () => {
    inputRef.current.focus();
  };
  
  return <input ref={inputRef} />;
}

// useRef for previous value
function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

// useMemo - memoize expensive calculations
function ExpensiveComponent({ items, filter }) {
  const filteredItems = useMemo(() => {
    return items.filter(item => item.includes(filter));
  }, [items, filter]);  // Only recalculate when deps change
  
  return <List items={filteredItems} />;
}

// useCallback - memoize functions
function Parent() {
  const [count, setCount] = useState(0);
  
  // Function reference stays same unless deps change
  const handleClick = useCallback(() => {
    console.log('Clicked!');
  }, []);  // Empty deps = never changes
  
  return <Child onClick={handleClick} />;
}

// useReducer - complex state logic
const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return initialState;
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  
  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}

useEffect

JSX
import { useState, useEffect } from 'react';

// Basic useEffect
function Component() {
  useEffect(() => {
    console.log('Component mounted or updated');
  });
  
  // Runs once on mount (like componentDidMount)
  useEffect(() => {
    console.log('Mounted');
  }, []);  // Empty dependency array
  
  // Runs when dependencies change
  useEffect(() => {
    console.log('Count changed:', count);
  }, [count]);
  
  // Cleanup function (like componentWillUnmount)
  useEffect(() => {
    const subscription = subscribeToData();
    
    return () => {
      subscription.unsubscribe();
    };
  }, []);
}

// Fetching data
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    let isMounted = true;  // Prevent state update on unmounted component
    
    async function fetchUser() {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();
        
        if (isMounted) {
          setUser(data);
          setError(null);
        }
      } catch (err) {
        if (isMounted) {
          setError(err.message);
        }
      } finally {
        if (isMounted) {
          setLoading(false);
        }
      }
    }
    
    fetchUser();
    
    return () => {
      isMounted = false;
    };
  }, [userId]);  // Re-fetch when userId changes
  
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;
  return <div>{user.name}</div>;
}

// Event listeners
useEffect(() => {
  const handleResize = () => {
    setWindowWidth(window.innerWidth);
  };
  
  window.addEventListener('resize', handleResize);
  
  return () => {
    window.removeEventListener('resize', handleResize);
  };
}, []);

Forms & Events

JSX
import { useState } from 'react';

// Controlled component
function LoginForm() {
  const [formData, setFormData] = useState({
    email: '',
    password: ''
  });
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form 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>
  );
}

// Uncontrolled component with useRef
function UncontrolledForm() {
  const inputRef = useRef();
  
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(inputRef.current.value);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input ref={inputRef} defaultValue="Initial" />
      <button type="submit">Submit</button>
    </form>
  );
}

// Event handling
function Button() {
  // Click event
  const handleClick = (e) => {
    console.log('Button clicked', e);
  };
  
  // Passing parameters
  const handleItemClick = (id) => (e) => {
    console.log('Item clicked:', id);
  };
  
  return (
    <div>
      <button onClick={handleClick}>Click me</button>
      <button onClick={handleItemClick(123)}>Item</button>
    </div>
  );
}

Context API

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

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

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

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

// 4. Use in components
function ThemedButton() {
  const { theme, toggleTheme } = useTheme();
  
  return (
    <button
      onClick={toggleTheme}
      style={{
        background: theme === 'light' ? '#fff' : '#333',
        color: theme === 'light' ? '#333' : '#fff'
      }}
    >
      Toggle Theme
    </button>
  );
}

// 5. Wrap app with provider
function App() {
  return (
    <ThemeProvider>
      <Header />
      <Main />
      <ThemedButton />
    </ThemeProvider>
  );
}

Common Patterns

JSX
// Custom 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];
}

// Usage
const [theme, setTheme] = useLocalStorage('theme', 'light');

// Compound Components Pattern
function Tabs({ children, defaultIndex = 0 }) {
  const [activeIndex, setActiveIndex] = useState(defaultIndex);
  
  return (
    <TabsContext.Provider value={{ activeIndex, setActiveIndex }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}

Tabs.List = function TabsList({ children }) {
  return <div className="tabs-list">{children}</div>;
};

Tabs.Tab = function Tab({ index, children }) {
  const { activeIndex, setActiveIndex } = useContext(TabsContext);
  return (
    <button 
      className={activeIndex === index ? 'active' : ''}
      onClick={() => setActiveIndex(index)}
    >
      {children}
    </button>
  );
};

// Render Props Pattern
function MouseTracker({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  
  useEffect(() => {
    const handleMove = (e) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };
    window.addEventListener('mousemove', handleMove);
    return () => window.removeEventListener('mousemove', handleMove);
  }, []);
  
  return render(position);
}

// Usage
<MouseTracker 
  render={({ x, y }) => <p>Mouse: {x}, {y}</p>} 
/>
Interview Tips:
• Understand the difference between state and props
• Know when to use useCallback vs useMemo
• Understand React's reconciliation algorithm
• Be familiar with React 18 features (Suspense, Transitions)