Table of Contents
- What is React?
- Setting Up Your React Environment
- Core Concepts: Components & JSX
- Props: Passing Data Between Components
- State: Managing Dynamic Data
- React Hooks: Simplifying State & Lifecycle
- Conditional Rendering & Lists
- State Management: Context API & Redux
- Routing with React Router
- Styling in React
- Testing React Applications
- Conclusion
- References
1. What is React?
React (officially React.js) is an open-source JavaScript library for building user interfaces (UIs), particularly single-page applications (SPAs). Unlike full-fledged frameworks like Angular or Vue, React focuses solely on the “view layer,” meaning it handles how data is displayed to users. This flexibility makes it easy to integrate with other libraries or tools for routing, state management, and backend communication.
Key Features of React:
- Declarative UI: Instead of writing imperative code to manipulate the DOM, you describe how the UI should look for a given state, and React updates the DOM automatically.
- Component-Based Architecture: UIs are built using reusable, independent components (e.g., a
Button,Navbar, orCard), making code easier to maintain and scale. - Virtual DOM: React creates a lightweight in-memory copy of the DOM (Virtual DOM) to minimize direct DOM manipulations, drastically improving performance.
- Unidirectional Data Flow: Data flows in one direction (from parent to child components), making state changes predictable and easier to debug.
2. Setting Up Your React Environment
To start building React apps, you’ll need Node.js (v14.0.0 or later) and npm (Node Package Manager) installed. If you don’t have them, download Node.js from nodejs.org.
Option 1: Create React App (CRA)
The easiest way to set up a React project is with Create React App (CRA), a toolchain that configures Webpack, Babel, and other build tools automatically.
# Create a new React app
npx create-react-app my-first-react-app
# Navigate into the project folder
cd my-first-react-app
# Start the development server
npm start
Your app will run at http://localhost:3000, and you’ll see a default React welcome page.
Option 2: Vite (Faster Alternative)
For faster builds and hot module replacement, use Vite, a modern build tool.
# Create a Vite React app
npm create vite@latest my-vite-react-app -- --template react
# Navigate into the project
cd my-vite-react-app
# Install dependencies
npm install
# Start the dev server
npm run dev
Vite is preferred for larger projects due to its speed, but CRA is simpler for beginners.
Project Structure Overview
Both tools generate a similar structure. Here’s a quick breakdown of key files:
src/App.js: The root component of your app.src/index.js: Renders the root component into the DOM.public/index.html: The HTML file where React injects your app.
3. Core Concepts: Components & JSX
Components: Building Blocks of React
Components are reusable pieces of UI. React has two types of components: functional components (using JavaScript functions) and class components (using ES6 classes). Today, functional components are preferred, especially with React Hooks (covered later).
Example: A Simple Functional Component
// src/components/Welcome.js
function Welcome() {
return (
<div>
<h1>Hello, React!</h1>
<p>Let's build something awesome.</p>
</div>
);
}
export default Welcome;
To use this component in App.js:
// src/App.js
import Welcome from './components/Welcome';
function App() {
return (
<div className="App">
<Welcome />
</div>
);
}
export default App;
JSX: JavaScript XML
JSX is a syntax extension for JavaScript that lets you write HTML-like code within JavaScript. It’s not required for React, but it makes writing UIs much cleaner.
JSX Rules:
- Use
classNameinstead ofclass(sinceclassis a reserved keyword in JavaScript). - Attributes are written in camelCase (e.g.,
onClick,tabIndex). - Self-closing tags (e.g.,
<img />,<input />) are required. - Embed JavaScript expressions using curly braces
{}.
Example:
function UserGreeting({ name }) {
const isLoggedIn = true;
return (
<div className="greeting">
<h2>Hello, {isLoggedIn ? name : 'Guest'}!</h2>
<p>Today is {new Date().toLocaleDateString()}</p>
</div>
);
}
4. Props: Passing Data Between Components
Props (short for “properties”) let you pass data from a parent component to a child component. Props are read-only—child components cannot modify the props they receive.
Passing Props to a Component
// Parent component (App.js)
function App() {
return (
<div>
<UserGreeting name="Alice" /> {/* Passing "name" prop */}
</div>
);
}
// Child component (UserGreeting.js)
function UserGreeting(props) { // Props are received as an argument
return <h1>Hello, {props.name}!</h1>;
}
// Or, using destructuring for cleaner code:
function UserGreeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
Default Props & Prop Validation
- Default Props: Set fallback values for props if they’re not provided.
- Prop Validation: Use
PropTypes(or TypeScript) to enforce prop types and catch errors early.
import PropTypes from 'prop-types'; // Install with: npm install prop-types
function UserGreeting({ name, age }) {
return (
<div>
<p>Name: {name}</p>
<p>Age: {age}</p>
</div>
);
}
// Default props
UserGreeting.defaultProps = {
age: 18, // Default age if not provided
};
// Prop types validation
UserGreeting.propTypes = {
name: PropTypes.string.isRequired, // name must be a string and is required
age: PropTypes.number, // age must be a number
};
export default UserGreeting;
5. State: Managing Dynamic Data
State is used to store and manage dynamic data within a component. Unlike props (passed from parents), state is internal to a component and can be updated.
Using the useState Hook
React’s useState hook lets you add state to functional components. It returns a state variable and a function to update it.
import { useState } from 'react';
function Counter() {
// Initialize state: count = 0, setCount = function to update count
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Key Notes About State:
- Immutability: Always replace state instead of modifying it directly. For example, update an array with
setItems([...items, newItem]), notitems.push(newItem). - Asynchronous Updates:
setCountis asynchronous—React batches state updates for performance.
6. React Hooks: Simplifying State & Lifecycle
Hooks (introduced in React 16.8) are functions that let you “hook into” React state and lifecycle features from functional components. They eliminate the need for class components.
Common Hooks
1. useState (Already Covered)
Manages state in functional components.
2. useEffect: Handle Side Effects
useEffect lets you perform side effects (e.g., data fetching, subscriptions, DOM manipulations) in functional components. It replaces class lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount.
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// Runs after render (like componentDidMount + componentDidUpdate)
useEffect(() => {
// Fetch user data
const fetchUser = async () => {
setLoading(true);
const response = await fetch(`https://api.example.com/users/${userId}`);
const data = await response.json();
setUser(data);
setLoading(false);
};
fetchUser();
// Cleanup function (like componentWillUnmount)
return () => {
// Cancel fetch if component unmounts (e.g., user navigates away)
// (In practice, use AbortController for fetch cleanup)
};
}, [userId]); // Re-run effect only if userId changes
if (loading) return <p>Loading...</p>;
if (!user) return <p>User not found</p>;
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
</div>
);
}
3. useContext: Access Context
useContext lets you access data from a React Context (a way to share state across components without prop drilling).
4. useReducer: Complex State Logic
For state logic involving multiple sub-values or complex updates, useReducer is often cleaner than useState. It uses a reducer function (like Redux) to manage state.
import { useReducer } from 'react';
// Reducer function: takes state and action, returns new state
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, { id: Date.now(), text: action.text, done: false }];
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id ? { ...todo, done: !todo.done } : todo
);
default:
return state;
}
}
function TodoList() {
const [todos, dispatch] = useReducer(todoReducer, []);
return (
<div>
<button onClick={() => dispatch({ type: 'ADD_TODO', text: 'Learn useReducer' })}>
Add Todo
</button>
<ul>
{todos.map(todo => (
<li
key={todo.id}
onClick={() => dispatch({ type: 'TOGGLE_TODO', id: todo.id })}
style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
>
{todo.text}
</li>
))}
</ul>
</div>
);
}
7. Conditional Rendering & Lists
Conditional Rendering
Render different UI elements based on state or props. Common approaches:
1. Ternary Operator
function Greeting({ isLoggedIn }) {
return isLoggedIn ? (
<p>Welcome back!</p>
) : (
<p>Please sign in.</p>
);
}
2. Logical && Operator
function Notification({ messages }) {
return (
<div>
{messages.length > 0 && (
<p>You have {messages.length} unread messages.</p>
)}
</div>
);
}
Lists & Keys
Render arrays of data using the map method. Always include a key prop for list items to help React identify which items have changed.
Example: Rendering a List
function ProductList({ products }) {
return (
<ul>
{products.map(product => (
<li key={product.id}> {/* Use unique IDs for keys */}
<h3>{product.name}</h3>
<p>Price: ${product.price}</p>
</li>
))}
</ul>
);
}
Why Keys? Keys help React optimize re-renders by tracking item identity. Avoid using indexes as keys (they can cause bugs if the list is reordered).
8. State Management: Context API & Redux
For small apps, useState and useReducer work well. For larger apps with shared state across many components, use:
Context API
Context API lets you share state globally without prop drilling (passing props through multiple levels).
Step 1: Create a Context
// src/contexts/ThemeContext.js
import { createContext, useContext, useState } from 'react';
// Create context with a default value
const ThemeContext = createContext();
// Create a provider component to wrap your app
export function ThemeProvider({ children }) {
const [darkMode, setDarkMode] = useState(false);
const toggleTheme = () => setDarkMode(!darkMode);
// Value to pass to consumers
const value = { darkMode, toggleTheme };
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}
// Custom hook to use the context (avoids repeating useContext)
export function useTheme() {
return useContext(ThemeContext);
}
Step 2: Use Context in Components
Wrap your app with ThemeProvider in index.js:
import { ThemeProvider } from './contexts/ThemeContext';
ReactDOM.createRoot(document.getElementById('root')).render(
<ThemeProvider>
<App />
</ThemeProvider>
);
Use the context in a component:
// src/components/Navbar.js
import { useTheme } from '../contexts/ThemeContext';
function Navbar() {
const { darkMode, toggleTheme } = useTheme();
return (
<nav style={{ background: darkMode ? 'black' : 'white', color: darkMode ? 'white' : 'black' }}>
<button onClick={toggleTheme}>
{darkMode ? 'Switch to Light Mode' : 'Switch to Dark Mode'}
</button>
</nav>
);
}
Redux
For large apps with complex state logic (e.g., e-commerce sites), Redux is a popular state management library. It uses a single global store and follows the principles of unidirectional data flow.
Redux Toolkit (Simplified Redux)
Redux Toolkit reduces boilerplate. Install it:
npm install @reduxjs/toolkit react-redux
Example: Counter Slice
// src/store/slices/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1; }, // Redux Toolkit uses Immer for mutable updates
decrement: (state) => { state.value -= 1; },
},
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
Configure the Store
// src/store/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './slices/counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
Use Redux in Components
Wrap your app with Provider from react-redux:
// src/index.js
import { Provider } from 'react-redux';
import { store } from './store/store';
ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<App />
</Provider>
);
Use useSelector and useDispatch in components:
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from '../store/slices/counterSlice';
function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
);
}
9. Routing with React Router
React Router is the standard library for handling navigation in React apps.
Installation
npm install react-router-dom
Basic Routing
Wrap your app with BrowserRouter and define routes with Routes and Route:
// src/App.js
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import NotFound from './pages/NotFound';
function App() {
return (
<Router>
<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 />} /> {/* Catch-all for 404 */}
</Routes>
</Router>
);
}
Dynamic Routes
Use URL parameters to pass data (e.g., /users/123).
// src/pages/User.js
import { useParams } from 'react-router-dom';
function User() {
const { userId } = useParams(); // Get userId from URL
return <h1>User ID: {userId}</h1>;
}
// Add to App.js Routes:
<Route path="/users/:userId" element={<User />} />
10. Styling in React
There are many ways to style React components:
1. CSS/SCSS Modules
Encapsulate styles to avoid conflicts.
// src/components/Button.module.css
.button {
padding: 10px 20px;
background: blue;
color: white;
}
// src/components/Button.js
import styles from './Button.module.css';
function Button() {
return <button className={styles.button}>Click Me</button>;
}
2. Styled Components
Write CSS directly in JavaScript using tagged template literals.
npm install styled-components
import styled from 'styled-components';
const StyledButton = styled.button`
padding: 10px 20px;
background: ${props => props.primary ? 'blue' : 'gray'};
color: white;
border: none;
border-radius: 4px;
`;
function Button() {
return <StyledButton primary>Primary Button</StyledButton>;
}
3. Tailwind CSS
A utility-first framework for rapid styling.
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Configure tailwind.config.js to include your components, then use utility classes:
function Card() {
return (
<div className="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-md flex items-center space-x-4">
<div>
<div className="text-xl font-medium text-black">ChitChat</div>
<p className="text-gray-500">You have a new message!</p>
</div>
</div>
);
}
11. Testing React Applications
Testing ensures your components work as expected. Use:
Jest + React Testing Library
Jest is a test runner, and React Testing Library helps render components and interact with them like a user.
Example Test for a Counter Component
// src/components/Counter.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('increments count when button is clicked', () => {
render(<Counter />);
// Check initial count
expect(screen.getByText(/count: 0/i)).toBeInTheDocument();
// Click the increment button
fireEvent.click(screen.getByText('+'));
// Check updated count
expect(screen.getByText(/count: 1/i)).toBeInTheDocument();
});
Run tests with:
npm test
12. Conclusion
React is a powerful library for building dynamic UIs, and mastering it opens doors to endless frontend possibilities. In this guide, you’ve learned:
- React’s core concepts (components, JSX, props, state).
- How to use Hooks for state and lifecycle.
- State management with Context API and Redux.
- Routing with React Router.
- Styling and testing React apps.
To grow further:
- Explore Next.js for server-side rendering and static site generation.
- Learn advanced Hooks like
useCallback,useMemo, and custom Hooks. - Dive into TypeScript for type safety.
- Build real projects (e.g., a todo app, weather dashboard, or e-commerce site).
Happy coding! 🚀