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:
Why Use React?
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:
className instead of classhtmlFor instead of forComponents
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
Additional Resources
Official Documentation:
Popular Libraries:
Learning Resources: