What is React?

React is a JavaScript library for building user interfaces, particularly single-page applications. It was developed by Facebook (now Meta) and is maintained by both Facebook and a community of individual developers and companies. React allows developers to create large web applications that can change data without reloading the page.
React is not a full-fledged framework like Angular or Vue. It’s focused specifically on the view layer of an application, making it more flexible but requiring additional libraries for full application development.

Why Use React?

Component-Based

React is built around the concept of reusable components, making it easier to manage complex UIs and promote code reuse.

Virtual DOM

React uses a virtual DOM to optimize rendering performance by minimizing direct manipulation of the actual DOM.

Declarative Syntax

React’s declarative approach makes your code more predictable and easier to debug.

Strong Ecosystem

React has a vast ecosystem of libraries, tools, and community support.

Getting Started with React

Setting Up a React Project

The easiest way to start a new React project is using Create React App, a tool that sets up a modern React application with a good default configuration:
# Using npx (recommended)
npx create-react-app my-react-app

# Or using Vite for a faster, more modern setup
npm create vite@latest my-react-app -- --template react

# Navigate to the project directory
cd my-react-app

# Start the development server
npm start  # for Create React App
# or
npm run dev  # for Vite

React Project Structure

A typical Create React App project structure looks like this:
my-react-app/
├── node_modules/
├── public/
│   ├── favicon.ico
│   ├── index.html
│   └── manifest.json
├── src/
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── index.css
│   ├── index.js
│   ├── logo.svg
│   └── reportWebVitals.js
├── .gitignore
├── package.json
└── README.md

Core React Concepts

Components

Components are the building blocks of React applications. They are reusable pieces of code that return React elements describing what should appear on the screen.

Function Components

// Simple function component
function Greeting(props) {
  return <h1>Hello, {props.name}!</h1>;
}

// Arrow function component
const Button = ({ onClick, children }) => (
  <button onClick={onClick}>
    {children}
  </button>
);

// Using the components
function App() {
  return (
    <div>
      <Greeting name="World" />
      <Button onClick={() => alert('Clicked!')}>Click Me</Button>
    </div>
  );
}

Class Components

import React, { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    this.increment = this.increment.bind(this);
  }

  increment() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}
Function components are now preferred over class components in modern React development, especially with the introduction of Hooks.

JSX

JSX (JavaScript XML) is a syntax extension for JavaScript that looks similar to HTML but allows you to write HTML-like code in your JavaScript files.
const element = <h1>Hello, world!</h1>;

// JSX with expressions
const name = 'John';
const greeting = <h1>Hello, {name}!</h1>;

// JSX with attributes
const link = <a href="https://reactjs.org" className="link">React Website</a>;

// JSX with children
const container = (
  <div>
    <h1>Title</h1>
    <p>Paragraph</p>
  </div>
);
JSX is transformed into regular JavaScript function calls by tools like Babel:
// This JSX
const element = <h1 className="greeting">Hello, world!</h1>;

// Gets transformed into this JavaScript
const element = React.createElement(
  'h1',
  { className: 'greeting' },
  'Hello, world!'
);

Props

Props (short for properties) are inputs to React components. They are passed from parent to child components and are read-only.
// Parent component passing props
function App() {
  return (
    <div>
      <UserProfile 
        name="Jane Doe" 
        role="Developer" 
        isActive={true} 
        skills={['React', 'JavaScript', 'CSS']} 
      />
    </div>
  );
}

// Child component receiving props
function UserProfile(props) {
  return (
    <div className="user-profile">
      <h2>{props.name}</h2>
      <p>Role: {props.role}</p>
      <p>Status: {props.isActive ? 'Active' : 'Inactive'}</p>
      <p>Skills: {props.skills.join(', ')}</p>
    </div>
  );
}

// Using destructuring for cleaner code
function UserProfile({ name, role, isActive, skills }) {
  return (
    <div className="user-profile">
      <h2>{name}</h2>
      <p>Role: {role}</p>
      <p>Status: {isActive ? 'Active' : 'Inactive'}</p>
      <p>Skills: {skills.join(', ')}</p>
    </div>
  );
}

State

State is a built-in object that contains data or information about the component. Unlike props, state can be changed.

State with Hooks (Function Components)

import React, { useState } from 'react';

function Counter() {
  // Declare a state variable named 'count' with initial value 0
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

State with Class Components

import React, { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

Lifecycle and Effects

React components have a lifecycle - they are created (mounted), updated, and destroyed (unmounted). You can hook into these phases to run code at specific times.

Lifecycle with Hooks (Function Components)

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

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  // Similar to componentDidMount and componentDidUpdate
  useEffect(() => {
    // This function runs after the component mounts and when userId changes
    setLoading(true);
    
    fetch(`https://api.example.com/users/${userId}`)
      .then(response => response.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      });
      
    // Cleanup function (similar to componentWillUnmount)
    return () => {
      // Clean up any subscriptions or timers here
      console.log('Component is unmounting or userId is changing');
    };
  }, [userId]); // Only re-run if userId changes

  if (loading) return <div>Loading...</div>;
  if (!user) return <div>User not found</div>;

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

Lifecycle with Class Components

import React, { Component } from 'react';

class UserProfile extends Component {
  constructor(props) {
    super(props);
    this.state = {
      user: null,
      loading: true
    };
  }

  componentDidMount() {
    // Runs after the component is mounted
    this.fetchUser();
  }

  componentDidUpdate(prevProps) {
    // Runs when the component updates
    if (prevProps.userId !== this.props.userId) {
      this.fetchUser();
    }
  }

  componentWillUnmount() {
    // Runs before the component is unmounted
    console.log('Component is unmounting');
    // Clean up subscriptions, timers, etc.
  }

  fetchUser() {
    this.setState({ loading: true });
    
    fetch(`https://api.example.com/users/${this.props.userId}`)
      .then(response => response.json())
      .then(data => {
        this.setState({
          user: data,
          loading: false
        });
      });
  }

  render() {
    const { loading, user } = this.state;
    
    if (loading) return <div>Loading...</div>;
    if (!user) return <div>User not found</div>;

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

React Hooks

Hooks are functions that let you “hook into” React state and lifecycle features from function components. They were introduced in React 16.8.

Basic Hooks

useState

import React, { useState } from 'react';

function Form() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({ name, email });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Name"
      />
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      <button type="submit">Submit</button>
    </form>
  );
}

useEffect

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

function WindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    
    // Add event listener
    window.addEventListener('resize', handleResize);
    
    // Clean up
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // Empty dependency array means this effect runs once on mount

  return <div>Window width: {width}px</div>;
}

useContext

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

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

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

// Consumer component
function ThemedButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);
  
  return (
    <button
      onClick={toggleTheme}
      style={{
        backgroundColor: theme === 'light' ? '#fff' : '#333',
        color: theme === 'light' ? '#333' : '#fff',
        padding: '10px 15px',
        border: '1px solid #ccc',
        borderRadius: '4px'
      }}
    >
      Toggle Theme
    </button>
  );
}

// App component
function App() {
  return (
    <ThemeProvider>
      <div style={{ padding: '20px' }}>
        <h1>Theme Example</h1>
        <ThemedButton />
      </div>
    </ThemeProvider>
  );
}

Additional Hooks

useReducer

import React, { 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:
      return state;
  }
}

function Counter() {
  // Initialize state with useReducer
  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>
  );
}

useMemo and useCallback

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

function ExpensiveCalculation({ a, b }) {
  // useMemo memoizes the result of a computation
  const result = useMemo(() => {
    console.log('Computing result...');
    // Simulate expensive calculation
    return a * b;
  }, [a, b]); // Only recompute if a or b changes

  // useCallback memoizes a function
  const handleClick = useCallback(() => {
    console.log(`Result is ${result}`);
  }, [result]); // Only recreate if result changes

  return (
    <div>
      <p>Result: {result}</p>
      <button onClick={handleClick}>Log Result</button>
    </div>
  );
}

Handling Events

React events are named using camelCase and passed as functions rather than strings.
function Button() {
  const handleClick = (e) => {
    e.preventDefault(); // Prevent default behavior
    console.log('Button clicked!');
  };

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  );
}

// Passing arguments to event handlers
function ItemList() {
  const handleItemClick = (id, e) => {
    console.log(`Item ${id} clicked`, e);
  };

  return (
    <ul>
      <li onClick={(e) => handleItemClick(1, e)}>Item 1</li>
      <li onClick={(e) => handleItemClick(2, e)}>Item 2</li>
    </ul>
  );
}

Forms in React

React provides two approaches to handling forms: controlled and uncontrolled components.

Controlled Components

In controlled components, form data is handled by React state.
import React, { useState } from 'react';

function LoginForm() {
  const [formData, setFormData] = useState({
    username: '',
    password: '',
    rememberMe: false
  });

  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    setFormData(prevData => ({
      ...prevData,
      [name]: type === 'checkbox' ? checked : value
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted:', formData);
    // Send data to server, etc.
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="username">Username:</label>
        <input
          type="text"
          id="username"
          name="username"
          value={formData.username}
          onChange={handleChange}
        />
      </div>
      
      <div>
        <label htmlFor="password">Password:</label>
        <input
          type="password"
          id="password"
          name="password"
          value={formData.password}
          onChange={handleChange}
        />
      </div>
      
      <div>
        <label>
          <input
            type="checkbox"
            name="rememberMe"
            checked={formData.rememberMe}
            onChange={handleChange}
          />
          Remember me
        </label>
      </div>
      
      <button type="submit">Login</button>
    </form>
  );
}

Uncontrolled Components

Uncontrolled components maintain their own state in the DOM.
import React, { useRef } from 'react';

function FileUploadForm() {
  const fileInputRef = useRef(null);
  const nameInputRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    
    const name = nameInputRef.current.value;
    const file = fileInputRef.current.files[0];
    
    console.log('Submitted:', { name, file });
    // Process the file upload
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">Name:</label>
        <input
          type="text"
          id="name"
          ref={nameInputRef}
          defaultValue=""
        />
      </div>
      
      <div>
        <label htmlFor="file">Upload file:</label>
        <input
          type="file"
          id="file"
          ref={fileInputRef}
        />
      </div>
      
      <button type="submit">Upload</button>
    </form>
  );
}

Styling in React

React offers several approaches to styling components:

CSS Stylesheets

// In App.css
.button {
  background-color: #4CAF50;
  color: white;
  padding: 10px 15px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.button:hover {
  background-color: #45a049;
}

// In App.js
import React from 'react';
import './App.css';

function App() {
  return (
    <button className="button">Click Me</button>
  );
}

Inline Styles

function Button() {
  const buttonStyle = {
    backgroundColor: '#4CAF50',
    color: 'white',
    padding: '10px 15px',
    border: 'none',
    borderRadius: '4px',
    cursor: 'pointer'
  };

  return (
    <button style={buttonStyle}>Click Me</button>
  );
}

CSS Modules

// In Button.module.css
.button {
  background-color: #4CAF50;
  color: white;
  padding: 10px 15px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

// In Button.js
import React from 'react';
import styles from './Button.module.css';

function Button() {
  return (
    <button className={styles.button}>Click Me</button>
  );
}

CSS-in-JS Libraries

// Using styled-components
import React from 'react';
import styled from 'styled-components';

const StyledButton = styled.button`
  background-color: #4CAF50;
  color: white;
  padding: 10px 15px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  
  &:hover {
    background-color: #45a049;
  }
`;

function Button() {
  return (
    <StyledButton>Click Me</StyledButton>
  );
}

React Router

React Router is the standard routing library for React applications, enabling navigation between different components.
import React from 'react';
import { BrowserRouter, Routes, Route, Link, useParams } from 'react-router-dom';

// Home component
function Home() {
  return <h2>Home Page</h2>;
}

// About component
function About() {
  return <h2>About Page</h2>;
}

// User profile component with dynamic routing
function UserProfile() {
  const { userId } = useParams();
  return <h2>User Profile: {userId}</h2>;
}

// Not found component
function NotFound() {
  return <h2>404 - Page Not Found</h2>;
}

// Navigation component
function Navigation() {
  return (
    <nav>
      <ul>
        <li><Link to="/">Home</Link></li>
        <li><Link to="/about">About</Link></li>
        <li><Link to="/users/1">User 1</Link></li>
        <li><Link to="/users/2">User 2</Link></li>
      </ul>
    </nav>
  );
}

// Main App component
function App() {
  return (
    <BrowserRouter>
      <div>
        <Navigation />
        <hr />
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/users/:userId" element={<UserProfile />} />
          <Route path="*" element={<NotFound />} />
        </Routes>
      </div>
    </BrowserRouter>
  );
}

State Management

For complex applications, you might need more advanced state management solutions beyond React’s built-in state.

Context API

For simpler applications or when prop drilling becomes an issue:
import React, { createContext, useContext, useReducer } from 'react';

// Create context
const TodoContext = createContext();

// Reducer function
function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, { id: Date.now(), text: action.payload, completed: false }];
    case 'TOGGLE_TODO':
      return state.map(todo =>
        todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
      );
    case 'DELETE_TODO':
      return state.filter(todo => todo.id !== action.payload);
    default:
      return state;
  }
}

// Provider component
function TodoProvider({ children }) {
  const [todos, dispatch] = useReducer(todoReducer, []);
  
  return (
    <TodoContext.Provider value={{ todos, dispatch }}>
      {children}
    </TodoContext.Provider>
  );
}

// Custom hook to use the todo context
function useTodo() {
  const context = useContext(TodoContext);
  if (!context) {
    throw new Error('useTodo must be used within a TodoProvider');
  }
  return context;
}

// Todo list component
function TodoList() {
  const { todos, dispatch } = useTodo();
  
  return (
    <ul>
      {todos.map(todo => (
        <li
          key={todo.id}
          style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
        >
          <span onClick={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}>
            {todo.text}
          </span>
          <button onClick={() => dispatch({ type: 'DELETE_TODO', payload: todo.id })}>
            Delete
          </button>
        </li>
      ))}
    </ul>
  );
}

// Add todo form
function AddTodo() {
  const { dispatch } = useTodo();
  const [text, setText] = React.useState('');
  
  const handleSubmit = (e) => {
    e.preventDefault();
    if (text.trim()) {
      dispatch({ type: 'ADD_TODO', payload: text });
      setText('');
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Add a todo"
      />
      <button type="submit">Add</button>
    </form>
  );
}

// App component
function App() {
  return (
    <TodoProvider>
      <div>
        <h1>Todo List</h1>
        <AddTodo />
        <TodoList />
      </div>
    </TodoProvider>
  );
}

Redux

For larger applications with complex state management needs:
// This is a simplified example of Redux with React
import React from 'react';
import { Provider, useSelector, useDispatch } from 'react-redux';
import { createStore } from 'redux';

// Action types
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

// Action creators
const increment = () => ({ type: INCREMENT });
const decrement = () => ({ type: DECREMENT });

// Reducer
function counterReducer(state = { count: 0 }, action) {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + 1 };
    case DECREMENT:
      return { count: state.count - 1 };
    default:
      return state;
  }
}

// Create store
const store = createStore(counterReducer);

// Counter component
function Counter() {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();
  
  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  );
}

// App component
function App() {
  return (
    <Provider store={store}>
      <div>
        <h1>Redux Counter</h1>
        <Counter />
      </div>
    </Provider>
  );
}

Testing React Applications

Testing is an essential part of developing robust React applications. Common testing libraries include Jest and React Testing Library.
// Button.js
import React from 'react';

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

export default Button;

// Button.test.js
import React from 'react';
import { render, fireEvent, screen } from '@testing-library/react';
import Button from './Button';

test('renders button with correct text', () => {
  render(<Button>Click Me</Button>);
  const buttonElement = screen.getByText(/click me/i);
  expect(buttonElement).toBeInTheDocument();
});

test('calls onClick handler when clicked', () => {
  const handleClick = jest.fn();
  render(<Button onClick={handleClick}>Click Me</Button>);
  
  fireEvent.click(screen.getByText(/click me/i));
  expect(handleClick).toHaveBeenCalledTimes(1);
});

Best Practices

  • Use function components with hooks instead of class components
  • Keep components small and focused on a single responsibility
  • Use prop types or TypeScript for type checking
  • Lift state up to the nearest common ancestor when multiple components need the same data
  • Use keys when rendering lists to help React identify which items have changed
  • Avoid using indexes as keys when the list can reorder
  • Memoize expensive calculations with useMemo and useCallback
  • Use React.lazy and Suspense for code-splitting
  • Follow the container/presentational pattern to separate logic from UI
  • Use error boundaries to catch and handle errors gracefully

Next Steps

Now that you understand the basics of React, you can: