A comprehensive guide to React fundamentals, hooks, patterns, and best practices.
Table of Contents
- Setup & Installation
- Components
- JSX
- Props
- State
- Hooks
- Event Handling
- Conditional Rendering
- Lists & Keys
- Forms
- Lifecycle Methods (Class Components)
- Context API
- Refs
- Performance Optimization
- Error Boundaries
- Portals
- Code Splitting
- Common Patterns
- Best Practices
Setup & Installation
Create React App
npx create-react-app my-app
cd my-app
npm startBashVite (Recommended – Faster)
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run devBashManual Setup with Webpack
npm init -y
npm install react react-dom
npm install --save-dev webpack webpack-cli webpack-dev-server
npm install --save-dev @babel/core @babel/preset-react babel-loaderBashComponents
Functional Component
// Basic functional component
function Welcome() {
return <h1>Hello, World!</h1>;
}
// Arrow function component
const Welcome = () => {
return <h1>Hello, World!</h1>;
};
// Implicit return
const Welcome = () => <h1>Hello, World!</h1>;
// With props
const Welcome = ({ name, age }) => {
return (
<div>
<h1>Hello, {name}!</h1>
<p>Age: {age}</p>
</div>
);
};JSXClass Component (Legacy)
import React, { Component } from 'react';
class Welcome extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
// With state and lifecycle
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
componentDidMount() {
console.log('Component mounted');
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Increment
</button>
</div>
);
}
}JSXJSX
Basic Syntax
// JavaScript expressions in JSX
const name = 'John';
const element = <h1>Hello, {name}!</h1>;
// Attributes (camelCase)
const element = <div className="container" id="main"></div>;
// Inline styles (object)
const element = <div style={{ color: 'red', fontSize: '20px' }}>Text</div>;
// Self-closing tags
const element = <img src="image.jpg" alt="Description" />;
// Fragments (no extra DOM node)
const element = (
<>
<h1>Title</h1>
<p>Paragraph</p>
</>
);
// React.Fragment (with key)
const element = (
<React.Fragment key={item.id}>
<h1>Title</h1>
</React.Fragment>
);JSXComments in JSX
const element = (
<div>
{/* Single line comment */}
<h1>Title</h1>
{/*
Multi-line
comment
*/}
</div>
);JSXProps
Passing Props
// Parent component
function App() {
return <Welcome name="John" age={30} isAdmin={true} />;
}
// Child component
function Welcome({ name, age, isAdmin }) {
return (
<div>
<h1>Hello, {name}!</h1>
<p>Age: {age}</p>
{isAdmin && <span>Admin User</span>}
</div>
);
}JSXDefault Props
// Using default parameters
function Welcome({ name = 'Guest', age = 0 }) {
return <h1>Hello, {name}! Age: {age}</h1>;
}
// Using defaultProps (older pattern)
Welcome.defaultProps = {
name: 'Guest',
age: 0
};JSXProps Spreading
const props = { name: 'John', age: 30 };
<Welcome {...props} />
// Combining with other props
<Welcome {...props} isAdmin={true} />JSXProps Children
// Parent
function Card({ children }) {
return <div className="card">{children}</div>;
}
// Usage
<Card>
<h1>Title</h1>
<p>Content</p>
</Card>JSXPropTypes (Type Checking)
import PropTypes from 'prop-types';
function Welcome({ name, age, isAdmin }) {
return <h1>Hello, {name}!</h1>;
}
Welcome.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
isAdmin: PropTypes.bool,
callback: PropTypes.func,
items: PropTypes.arrayOf(PropTypes.string),
user: PropTypes.shape({
name: PropTypes.string,
age: PropTypes.number
})
};JSXState
useState Hook
import { useState } from 'react';
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>
);
}JSXMultiple State Variables
function Form() {
const [name, setName] = useState('');
const [age, setAge] = useState(0);
const [isSubscribed, setIsSubscribed] = useState(false);
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<input value={age} onChange={(e) => setAge(Number(e.target.value))} />
<input
type="checkbox"
checked={isSubscribed}
onChange={(e) => setIsSubscribed(e.target.checked)}
/>
</div>
);
}JSXState 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} />
<input name="email" value={user.email} onChange={handleChange} />
<input name="age" value={user.age} onChange={handleChange} />
</form>
);
}JSXState with Arrays
function TodoList() {
const [todos, setTodos] = useState([]);
// Add item
const addTodo = (text) => {
setTodos([...todos, { id: Date.now(), text }]);
};
// Remove item
const removeTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
// Update item
const updateTodo = (id, newText) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, text: newText } : todo
));
};
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}JSXFunctional Updates
// When new state depends on previous state
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(prevCount => prevCount + 1);
};
const incrementMultiple = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1); // Will increment by 3
};
return <button onClick={increment}>Count: {count}</button>;
}JSXHooks
useState
const [state, setState] = useState(initialValue);
const [state, setState] = useState(() => expensiveComputation());JSXuseEffect
import { useEffect } from 'react';
// Runs after every render
useEffect(() => {
console.log('Component rendered');
});
// Runs only on mount
useEffect(() => {
console.log('Component mounted');
}, []);
// Runs when dependencies change
useEffect(() => {
console.log('Count changed:', count);
}, [count]);
// Cleanup function
useEffect(() => {
const timer = setInterval(() => {
console.log('Tick');
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
// Multiple effects
useEffect(() => {
// Effect 1
}, [dep1]);
useEffect(() => {
// Effect 2
}, [dep2]);JSXuseContext
import { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
const theme = useContext(ThemeContext);
return <div>Current theme: {theme}</div>;
}JSXuseReducer
import { useReducer } from 'react';
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>
<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>
);
}JSXuseCallback
import { useCallback, useState } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// Memoized callback
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []); // Dependencies
return <Child onClick={handleClick} />;
}JSXuseMemo
import { useMemo, useState } from 'react';
function ExpensiveComponent({ items }) {
const expensiveValue = useMemo(() => {
console.log('Computing...');
return items.reduce((acc, item) => acc + item.value, 0);
}, [items]);
return <div>Total: {expensiveValue}</div>;
}JSXuseRef
import { useRef, useEffect } from 'react';
function TextInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} />;
}
// Storing mutable value
function Timer() {
const countRef = useRef(0);
const handleClick = () => {
countRef.current += 1;
console.log('Clicked:', countRef.current);
};
return <button onClick={handleClick}>Click</button>;
}JSXuseLayoutEffect
import { useLayoutEffect, useRef } from 'react';
function Component() {
const divRef = useRef(null);
// Runs synchronously before paint
useLayoutEffect(() => {
const height = divRef.current.offsetHeight;
console.log('Height:', height);
}, []);
return <div ref={divRef}>Content</div>;
}JSXuseImperativeHandle
import { forwardRef, useImperativeHandle, useRef } from 'react';
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
clear: () => {
inputRef.current.value = '';
}
}));
return <input ref={inputRef} />;
});
function Parent() {
const inputRef = useRef();
return (
<div>
<CustomInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
<button onClick={() => inputRef.current.clear()}>Clear</button>
</div>
);
}JSXCustom Hooks
// useFetch hook
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [url]);
return { data, loading, error };
}
// Usage
function App() {
const { data, loading, error } = useFetch('https://api.example.com/data');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{JSON.stringify(data)}</div>;
}
// useLocalStorage hook
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// useToggle hook
function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = () => setValue(v => !v);
return [value, toggle];
}
// usePrevious hook
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}JSXEvent Handling
Click Events
function Button() {
const handleClick = (e) => {
console.log('Button clicked', e);
};
return <button onClick={handleClick}>Click Me</button>;
}
// Inline
<button onClick={(e) => console.log('Clicked', e)}>Click</button>
// With arguments
<button onClick={() => handleClick('arg1', 'arg2')}>Click</button>JSXForm Events
function Form() {
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form submitted');
};
const handleChange = (e) => {
console.log('Input changed:', e.target.value);
};
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} />
<button type="submit">Submit</button>
</form>
);
}JSXKeyboard Events
function Input() {
const handleKeyDown = (e) => {
if (e.key === 'Enter') {
console.log('Enter pressed');
}
};
return <input onKeyDown={handleKeyDown} />;
}JSXMouse Events
function Box() {
return (
<div
onMouseEnter={() => console.log('Mouse entered')}
onMouseLeave={() => console.log('Mouse left')}
onMouseMove={(e) => console.log('Mouse moved', e.clientX, e.clientY)}
>
Hover over me
</div>
);
}JSXEvent Pooling (Legacy)
// In React 17+, event pooling is removed
function handleClick(e) {
console.log(e.type); // Works fine
setTimeout(() => {
console.log(e.type); // Also works in React 17+
}, 1000);
}JSXConditional Rendering
If-Else with Variable
function Greeting({ isLoggedIn }) {
let message;
if (isLoggedIn) {
message = <h1>Welcome back!</h1>;
} else {
message = <h1>Please sign in.</h1>;
}
return message;
}JSXTernary Operator
function Greeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? <h1>Welcome back!</h1> : <h1>Please sign in.</h1>}
</div>
);
}JSXLogical && Operator
function Mailbox({ unreadMessages }) {
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 && (
<h2>You have {unreadMessages.length} unread messages.</h2>
)}
</div>
);
}JSXNullish Coalescing
function Component({ value }) {
return <div>{value ?? 'Default value'}</div>;
}JSXSwitch Case (with IIFE)
function Component({ status }) {
return (
<div>
{(() => {
switch (status) {
case 'loading':
return <Loading />;
case 'error':
return <Error />;
case 'success':
return <Success />;
default:
return <div>Unknown status</div>;
}
})()}
</div>
);
}JSXEarly Return
function Component({ user }) {
if (!user) {
return <div>Please log in</div>;
}
if (user.role !== 'admin') {
return <div>Access denied</div>;
}
return <div>Admin Panel</div>;
}JSXLists & Keys
Basic List Rendering
function List() {
const items = ['Apple', 'Banana', 'Cherry'];
return (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
}JSXList with Objects
function UserList() {
const users = [
{ id: 1, name: 'John', age: 30 },
{ id: 2, name: 'Jane', age: 25 },
{ id: 3, name: 'Bob', age: 35 }
];
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name} - {user.age}
</li>
))}
</ul>
);
}JSXKeys Best Practices
// ✅ Good: Using unique IDs
<li key={item.id}>{item.name}</li>
// ❌ Bad: Using index (can cause issues with reordering)
<li key={index}>{item.name}</li>
// ✅ Good: Using compound key
<li key={`${item.category}-${item.id}`}>{item.name}</li>JSXFiltering Lists
function FilteredList() {
const [filter, setFilter] = useState('');
const items = ['Apple', 'Banana', 'Cherry', 'Date'];
const filteredItems = items.filter(item =>
item.toLowerCase().includes(filter.toLowerCase())
);
return (
<div>
<input value={filter} onChange={(e) => setFilter(e.target.value)} />
<ul>
{filteredItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}JSXForms
Controlled Components
function Form() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
console.log('Submitted:', { 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>
);
}JSXMultiple Inputs
function Form() {
const [formData, setFormData] = useState({
name: '',
email: '',
age: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Submitted:', formData);
};
return (
<form onSubmit={handleSubmit}>
<input name="name" value={formData.name} onChange={handleChange} />
<input name="email" value={formData.email} onChange={handleChange} />
<input name="age" value={formData.age} onChange={handleChange} />
<button type="submit">Submit</button>
</form>
);
}JSXCheckbox
function CheckboxForm() {
const [isChecked, setIsChecked] = useState(false);
return (
<label>
<input
type="checkbox"
checked={isChecked}
onChange={(e) => setIsChecked(e.target.checked)}
/>
Accept terms
</label>
);
}JSXRadio Buttons
function RadioForm() {
const [selected, setSelected] = useState('option1');
return (
<div>
<label>
<input
type="radio"
value="option1"
checked={selected === 'option1'}
onChange={(e) => setSelected(e.target.value)}
/>
Option 1
</label>
<label>
<input
type="radio"
value="option2"
checked={selected === 'option2'}
onChange={(e) => setSelected(e.target.value)}
/>
Option 2
</label>
</div>
);
}JSXSelect Dropdown
function SelectForm() {
const [selected, setSelected] = useState('');
return (
<select value={selected} onChange={(e) => setSelected(e.target.value)}>
<option value="">Select...</option>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
);
}JSXTextarea
function TextareaForm() {
const [text, setText] = useState('');
return (
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Enter text..."
/>
);
}JSXUncontrolled Components (with Refs)
function UncontrolledForm() {
const nameRef = useRef();
const emailRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
console.log('Name:', nameRef.current.value);
console.log('Email:', emailRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<input ref={nameRef} type="text" defaultValue="John" />
<input ref={emailRef} type="email" />
<button type="submit">Submit</button>
</form>
);
}JSXLifecycle Methods
Class Component Lifecycle
class LifecycleDemo extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
console.log('1. Constructor');
}
static getDerivedStateFromProps(props, state) {
console.log('2. getDerivedStateFromProps');
return null; // or return new state
}
componentDidMount() {
console.log('4. componentDidMount');
// Fetch data, set up subscriptions
}
shouldComponentUpdate(nextProps, nextState) {
console.log('5. shouldComponentUpdate');
return true; // or false to prevent re-render
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('6. getSnapshotBeforeUpdate');
return null; // or return snapshot value
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('7. componentDidUpdate');
// Operate on DOM, make network requests
}
componentWillUnmount() {
console.log('8. componentWillUnmount');
// Cleanup: clear timers, cancel requests
}
render() {
console.log('3. Render');
return <div>Count: {this.state.count}</div>;
}
}JSXFunctional Component Equivalent
function LifecycleDemo() {
// componentDidMount
useEffect(() => {
console.log('Component mounted');
}, []);
// componentDidUpdate
useEffect(() => {
console.log('Component updated');
});
// componentWillUnmount
useEffect(() => {
return () => {
console.log('Component will unmount');
};
}, []);
// Specific prop/state change
useEffect(() => {
console.log('Count changed');
}, [count]);
return <div>Component</div>;
}JSXContext API
Creating Context
import { createContext, useContext, useState } from 'react';
// Create context
const ThemeContext = createContext();
// Provider component
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// Custom hook for using context
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// Usage in components
function App() {
return (
<ThemeProvider>
<Toolbar />
</ThemeProvider>
);
}
function Toolbar() {
const { theme, toggleTheme } = useTheme();
return (
<div>
<p>Current theme: {theme}</p>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
}JSXMultiple Contexts
const UserContext = createContext();
const ThemeContext = createContext();
function App() {
return (
<UserContext.Provider value={user}>
<ThemeContext.Provider value={theme}>
<Main />
</ThemeContext.Provider>
</UserContext.Provider>
);
}
function Main() {
const user = useContext(UserContext);
const theme = useContext(ThemeContext);
return <div>User: {user.name}, Theme: {theme}</div>;
}JSXRefs
useRef for DOM Elements
function AutoFocusInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} />;
}JSXuseRef for Mutable Values
function Timer() {
const intervalRef = useRef(null);
const [count, setCount] = useState(0);
const startTimer = () => {
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
};
const stopTimer = () => {
clearInterval(intervalRef.current);
};
useEffect(() => {
return () => clearInterval(intervalRef.current);
}, []);
return (
<div>
<p>Count: {count}</p>
<button onClick={startTimer}>Start</button>
<button onClick={stopTimer}>Stop</button>
</div>
);
}JSXforwardRef
const FancyButton = forwardRef((props, ref) => (
<button ref={ref} className="fancy-button">
{props.children}
</button>
));
function Parent() {
const buttonRef = useRef();
const handleClick = () => {
buttonRef.current.focus();
};
return (
<div>
<FancyButton ref={buttonRef}>Click Me</FancyButton>
<button onClick={handleClick}>Focus the button</button>
</div>
);
}JSXCallback Refs
function MeasureExample() {
const [height, setHeight] = useState(0);
const measuredRef = useCallback(node => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<div>
<h1 ref={measuredRef}>Hello, world</h1>
<h2>The above header is {Math.round(height)}px tall</h2>
</div>
);
}JSXPerformance Optimization
React.memo
// Prevents re-render if props haven't changed
const ExpensiveComponent = React.memo(({ data }) => {
console.log('Rendering...');
return <div>{data}</div>;
});
// Custom comparison
const Component = React.memo(
({ data }) => <div>{data}</div>,
(prevProps, nextProps) => {
return prevProps.data === nextProps.data;
}
);JSXuseMemo
function ExpensiveComponent({ items }) {
// Only recomputes when items change
const total = useMemo(() => {
console.log('Computing total...');
return items.reduce((sum, item) => sum + item.price, 0);
}, [items]);
return <div>Total: ${total}</div>;
}JSXuseCallback
function Parent() {
const [count, setCount] = useState(0);
// Memoized callback - same reference across renders
const handleClick = useCallback(() => {
console.log('Clicked');
}, []);
return (
<div>
<Child onClick={handleClick} />
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
</div>
);
}
const Child = React.memo(({ onClick }) => {
console.log('Child rendered');
return <button onClick={onClick}>Click</button>;
});JSXCode Splitting with lazy
import { lazy, Suspense } from 'react';
const LazyComponent = lazy(() => import('./LazyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
}JSXVirtualization (React Window)
import { FixedSizeList } from 'react-window';
function VirtualList() {
const Row = ({ index, style }) => (
<div style={style}>Row {index}</div>
);
return (
<FixedSizeList
height={400}
itemCount={1000}
itemSize={35}
width="100%"
>
{Row}
</FixedSizeList>
);
}JSXError Boundaries
Class Component Error Boundary
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught:', error, errorInfo);
// Log to error reporting service
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// Usage
function App() {
return (
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
);
}JSXError Boundary with Fallback UI
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Error:', error);
console.error('Error Info:', errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div>
<h1>Oops! Something went wrong.</h1>
<p>{this.state.error?.message}</p>
<button onClick={() => this.setState({ hasError: false })}>
Try again
</button>
</div>
);
}
return this.props.children;
}
}JSXPortals
Creating a Portal
import { createPortal } from 'react-dom';
function Modal({ children, isOpen }) {
if (!isOpen) return null;
return createPortal(
<div className="modal-overlay">
<div className="modal">
{children}
</div>
</div>,
document.getElementById('modal-root')
);
}
// Usage
function App() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
<Modal isOpen={isOpen}>
<h1>Modal Content</h1>
<button onClick={() => setIsOpen(false)}>Close</button>
</Modal>
</div>
);
}JSXPortal with Event Bubbling
function Parent() {
const handleClick = () => {
console.log('Clicked in parent');
};
return (
<div onClick={handleClick}>
<Modal />
</div>
);
}
function Modal() {
// Clicks bubble to Parent even though rendered elsewhere
return createPortal(
<button>Click me</button>,
document.body
);
}JSXCode Splitting
Dynamic Import with lazy
import { lazy, Suspense } from 'react';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
);
}JSXNamed Exports
const MyComponent = lazy(() =>
import('./MyComponent').then(module => ({ default: module.MyComponent }))
);JSXPreloading
const LazyComponent = lazy(() => import('./LazyComponent'));
// Preload on hover
function Button() {
const handleMouseEnter = () => {
import('./LazyComponent'); // Preload
};
return <button onMouseEnter={handleMouseEnter}>Show Component</button>;
}JSXCommon Patterns
Higher-Order Components (HOC)
// HOC that adds loading prop
function withLoading(Component) {
return function WithLoadingComponent({ isLoading, ...props }) {
if (isLoading) return <div>Loading...</div>;
return <Component {...props} />;
};
}
// Usage
const ListWithLoading = withLoading(List);
<ListWithLoading isLoading={true} items={items} />JSXRender Props
function DataFetcher({ url, render }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
});
}, [url]);
return render({ data, loading });
}
// Usage
<DataFetcher
url="/api/users"
render={({ data, loading }) =>
loading ? <div>Loading...</div> : <div>{data}</div>
}
/>JSXCompound Components
const Tab = ({ children, isActive }) => (
<button className={isActive ? 'active' : ''}>
{children}
</button>
);
const TabPanel = ({ children, isActive }) => (
isActive ? <div>{children}</div> : null
);
function Tabs({ children }) {
const [activeIndex, setActiveIndex] = useState(0);
return (
<div>
<div className="tabs">
{React.Children.map(children, (child, index) =>
React.cloneElement(child.props.tab, {
isActive: index === activeIndex,
onClick: () => setActiveIndex(index)
})
)}
</div>
<div className="panels">
{React.Children.map(children, (child, index) =>
React.cloneElement(child, {
isActive: index === activeIndex
})
)}
</div>
</div>
);
}
// Usage
<Tabs>
<TabPanel tab={<Tab>Tab 1</Tab>}>Content 1</TabPanel>
<TabPanel tab={<Tab>Tab 2</Tab>}>Content 2</TabPanel>
</Tabs>JSXContainer/Presentational Pattern
// Presentational Component (dumb)
function UserList({ users, onDelete }) {
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name}
<button onClick={() => onDelete(user.id)}>Delete</button>
</li>
))}
</ul>
);
}
// Container Component (smart)
function UserListContainer() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(setUsers);
}, []);
const handleDelete = (id) => {
fetch(`/api/users/${id}`, { method: 'DELETE' })
.then(() => setUsers(users.filter(u => u.id !== id)));
};
return <UserList users={users} onDelete={handleDelete} />;
}JSXControlled vs Uncontrolled
// Controlled
function ControlledInput() {
const [value, setValue] = useState('');
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}
// Uncontrolled
function UncontrolledInput() {
const inputRef = useRef();
const handleSubmit = () => {
console.log(inputRef.current.value);
};
return <input ref={inputRef} defaultValue="Initial" />;
}JSXBest Practices
Component Organization
// 1. Imports
import { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Button } from './Button';
import './Component.css';
// 2. Component definition
function MyComponent({ title, data }) {
// 3. State hooks
const [count, setCount] = useState(0);
// 4. Effect hooks
useEffect(() => {
// Side effects
}, []);
// 5. Custom hooks
const customValue = useCustomHook();
// 6. Event handlers
const handleClick = () => {
setCount(count + 1);
};
// 7. Render helpers
const renderItem = (item) => <div>{item}</div>;
// 8. Early returns
if (!data) return <div>Loading...</div>;
// 9. Main render
return (
<div>
<h1>{title}</h1>
<button onClick={handleClick}>Count: {count}</button>
</div>
);
}
// 10. PropTypes
MyComponent.propTypes = {
title: PropTypes.string.isRequired,
data: PropTypes.array
};
// 11. Default props
MyComponent.defaultProps = {
data: []
};
// 12. Export
export default MyComponent;JSXNaming Conventions
// Components: PascalCase
function UserProfile() {}
// Functions/variables: camelCase
const handleClick = () => {};
const userName = 'John';
// Constants: UPPER_SNAKE_CASE
const MAX_COUNT = 100;
const API_URL = 'https://api.example.com';
// Custom hooks: use prefix
function useWindowSize() {}
function useFetch() {}
// Event handlers: handle prefix
const handleSubmit = () => {};
const handleChange = () => {};
// Boolean variables: is/has prefix
const isLoading = true;
const hasError = false;JSXFile Structure
src/
├── components/
│ ├── common/
│ │ ├── Button/
│ │ │ ├── Button.jsx
│ │ │ ├── Button.test.jsx
│ │ │ ├── Button.module.css
│ │ │ └── index.js
│ │ └── Input/
│ └── features/
│ └── UserProfile/
├── hooks/
│ ├── useAuth.js
│ ├── useFetch.js
│ └── useLocalStorage.js
├── context/
│ ├── AuthContext.jsx
│ └── ThemeContext.jsx
├── pages/
│ ├── Home.jsx
│ ├── About.jsx
│ └── Contact.jsx
├── utils/
│ ├── api.js
│ ├── helpers.js
│ └── constants.js
├── App.jsx
└── index.jsJSXKey Guidelines
// ✅ Do: Destructure props
function Component({ name, age }) {
return <div>{name}</div>;
}
// ❌ Don't: Use props object
function Component(props) {
return <div>{props.name}</div>;
}
// ✅ Do: Use meaningful variable names
const isUserLoggedIn = checkAuth();
const userList = fetchUsers();
// ❌ Don't: Use unclear abbreviations
const usr = getU();
const flg = chk();
// ✅ Do: Keep components small and focused
function UserCard({ user }) {
return (
<div>
<UserAvatar user={user} />
<UserInfo user={user} />
</div>
);
}
// ❌ Don't: Create large, multi-purpose components
function UserCard({ user }) {
// 500 lines of code...
}
// ✅ Do: Use composition
function Page() {
return (
<Layout>
<Header />
<Main />
<Footer />
</Layout>
);
}
// ✅ Do: Extract reusable logic to custom hooks
function useWindowSize() {
const [size, setSize] = useState({ width: 0, height: 0 });
// ...
return size;
}
// ✅ Do: Use functional updates for state
setCount(prev => prev + 1);
// ❌ Don't: Mutate state directly
count++; // Wrong!
setCount(count++); // Wrong!
// ✅ Do: Clean up effects
useEffect(() => {
const timer = setInterval(() => {}, 1000);
return () => clearInterval(timer);
}, []);
// ✅ Do: Handle loading and error states
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <Data data={data} />;JSXPerformance Tips
// 1. Use React.memo for expensive components
const ExpensiveComponent = React.memo(Component);
// 2. Use useMemo for expensive calculations
const value = useMemo(() => expensiveOperation(), [deps]);
// 3. Use useCallback for callbacks passed to children
const callback = useCallback(() => {}, [deps]);
// 4. Avoid inline object/array creation in render
// ❌ Don't
<Component style={{ margin: 10 }} />
<Component items={[1, 2, 3]} />
// ✅ Do
const style = { margin: 10 };
const items = [1, 2, 3];
<Component style={style} items={items} />
// 5. Use keys properly in lists
{items.map(item => <Item key={item.id} {...item} />)}
// 6. Lazy load components
const LazyComponent = lazy(() => import('./Component'));
// 7. Debounce expensive operations
const debouncedSearch = useMemo(
() => debounce(handleSearch, 300),
[]
);JSXAccessibility
// Use semantic HTML
<button onClick={handleClick}>Click</button>
// Not: <div onClick={handleClick}>Click</div>
// Add ARIA labels
<button aria-label="Close modal" onClick={onClose}>×</button>
// Use proper form labels
<label htmlFor="email">Email</label>
<input id="email" type="email" />
// Add alt text to images
<img src="avatar.jpg" alt="User profile picture" />
// Manage focus
const inputRef = useRef();
useEffect(() => {
inputRef.current.focus();
}, []);
// Keyboard navigation
<div
role="button"
tabIndex={0}
onClick={handleClick}
onKeyDown={(e) => e.key === 'Enter' && handleClick()}
>
Click me
</div>JSXCommon Hooks Patterns
Fetch Data Pattern
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
setLoading(true);
fetch(url)
.then(res => res.json())
.then(data => {
if (!cancelled) {
setData(data);
setLoading(false);
}
})
.catch(error => {
if (!cancelled) {
setError(error);
setLoading(false);
}
});
return () => {
cancelled = true;
};
}, [url]);
return { data, loading, error };
}JSXForm Handling Pattern
function useForm(initialValues) {
const [values, setValues] = useState(initialValues);
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setValues({
...values,
[name]: type === 'checkbox' ? checked : value
});
};
const reset = () => setValues(initialValues);
return { values, handleChange, reset };
}
// Usage
function Form() {
const { values, handleChange, reset } = useForm({
name: '',
email: ''
});
return (
<form>
<input name="name" value={values.name} onChange={handleChange} />
<input name="email" value={values.email} onChange={handleChange} />
<button type="button" onClick={reset}>Reset</button>
</form>
);
}JSXTypeScript with React
Component Props
interface Props {
name: string;
age?: number;
isActive: boolean;
onClick: () => void;
}
const Component: React.FC<Props> = ({ name, age = 0, isActive, onClick }) => {
return <div>{name}</div>;
};JSXuseState with TypeScript
const [count, setCount] = useState<number>(0);
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<string[]>([]);JSXEvent Types
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log(e);
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
};JSXThis cheatsheet covers the essential React concepts, patterns, and best practices. Keep it handy for quick reference while building React applications!
Discover more from Altgr Blog
Subscribe to get the latest posts sent to your email.
