Introduction to React
React is a JavaScript library for building user interfaces. Created by Facebook, it uses a component-based architecture and virtual DOM for efficient rendering.
Key Concepts
- Component-Based: Build encapsulated components that manage their own state
- Declarative: Describe what UI should look like for any given state
- Virtual DOM: Efficient updates by comparing virtual representations
- Unidirectional Data Flow: Data flows down from parent to child
JSX
// Creating a React app
// npx create-react-app my-app
// npx create-vite@latest my-app --template react
// Basic React component
import React from 'react';
function App() {
return (
<div className="app">
<h1>Hello, React!</h1>
</div>
);
}
export default App;
Components
JSX
// Functional Component (Recommended)
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// Arrow Function Component
const Greeting = ({ name }) => <h1>Hello, {name}!</h1>;
// With default props
const Button = ({ text = "Click me", onClick }) => (
<button onClick={onClick}>{text}</button>
);
// Class Component (Legacy)
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
// Component Composition
function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-content">
{children}
</div>
</div>
);
}
// Usage
<Card title="Welcome">
<p>This is the card content</p>
<Button text="Learn More" />
</Card>
JSX
JSX
// JSX = JavaScript XML
// Gets compiled to React.createElement()
// Embedding expressions
const name = "Alice";
const element = <h1>Hello, {name}!</h1>;
// Attributes
<img src={imageUrl} alt="description" />
<div className="container"> {/* class → className */}
<label htmlFor="name"> {/* for → htmlFor */}
// Inline styles (object notation)
<div style={{ color: 'red', fontSize: '16px' }}>Styled</div>
// Conditional rendering
{isLoggedIn && <UserDashboard />}
{isLoggedIn ? <Dashboard /> : <Login />}
// Rendering lists
const items = ['Apple', 'Banana', 'Cherry'];
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
// Better key - use unique ID
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
// Fragments - return multiple elements
return (
<>
<Header />
<Main />
<Footer />
</>
);
// Or with key
return (
<React.Fragment key={id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
);
State & Props
JSX
import { useState } from 'react';
// Props - data passed from parent
function Welcome({ name, age }) {
return <p>{name} is {age} years old</p>;
}
// Usage
<Welcome name="Alice" age={25} />
// Props with children
function Container({ children, className }) {
return <div className={className}>{children}</div>;
}
// State - component's internal data
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
// Multiple state variables
function Form() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
// ...
}
// State with objects
function UserForm() {
const [user, setUser] = useState({
name: '',
email: '',
age: 0
});
// IMPORTANT: Always create new object
const updateName = (name) => {
setUser(prev => ({ ...prev, name }));
};
// DON'T mutate directly
// user.name = 'Alice'; // WRONG!
}
// State with arrays
function TodoList() {
const [todos, setTodos] = useState([]);
const addTodo = (text) => {
setTodos(prev => [...prev, { id: Date.now(), text }]);
};
const removeTodo = (id) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
};
const updateTodo = (id, newText) => {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, text: newText } : todo
));
};
}
React Hooks
JSX
import {
useState, useEffect, useRef,
useMemo, useCallback, useReducer
} from 'react';
// useState - manage state
const [value, setValue] = useState(initialValue);
// Lazy initialization (for expensive computations)
const [state, setState] = useState(() => {
return expensiveComputation();
});
// useRef - persist value without re-render
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return <input ref={inputRef} />;
}
// useRef for previous value
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
// useMemo - memoize expensive calculations
function ExpensiveComponent({ items, filter }) {
const filteredItems = useMemo(() => {
return items.filter(item => item.includes(filter));
}, [items, filter]); // Only recalculate when deps change
return <List items={filteredItems} />;
}
// useCallback - memoize functions
function Parent() {
const [count, setCount] = useState(0);
// Function reference stays same unless deps change
const handleClick = useCallback(() => {
console.log('Clicked!');
}, []); // Empty deps = never changes
return <Child onClick={handleClick} />;
}
// useReducer - complex state logic
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return initialState;
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
useEffect
JSX
import { useState, useEffect } from 'react';
// Basic useEffect
function Component() {
useEffect(() => {
console.log('Component mounted or updated');
});
// Runs once on mount (like componentDidMount)
useEffect(() => {
console.log('Mounted');
}, []); // Empty dependency array
// Runs when dependencies change
useEffect(() => {
console.log('Count changed:', count);
}, [count]);
// Cleanup function (like componentWillUnmount)
useEffect(() => {
const subscription = subscribeToData();
return () => {
subscription.unsubscribe();
};
}, []);
}
// Fetching data
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true; // Prevent state update on unmounted component
async function fetchUser() {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
if (isMounted) {
setUser(data);
setError(null);
}
} catch (err) {
if (isMounted) {
setError(err.message);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
}
fetchUser();
return () => {
isMounted = false;
};
}, [userId]); // Re-fetch when userId changes
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return <div>{user.name}</div>;
}
// Event listeners
useEffect(() => {
const handleResize = () => {
setWindowWidth(window.innerWidth);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
Forms & Events
JSX
import { useState } from 'react';
// Controlled component
function LoginForm() {
const [formData, setFormData] = useState({
email: '',
password: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form submitted:', formData);
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
placeholder="Password"
/>
<button type="submit">Login</button>
</form>
);
}
// Uncontrolled component with useRef
function UncontrolledForm() {
const inputRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
console.log(inputRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<input ref={inputRef} defaultValue="Initial" />
<button type="submit">Submit</button>
</form>
);
}
// Event handling
function Button() {
// Click event
const handleClick = (e) => {
console.log('Button clicked', e);
};
// Passing parameters
const handleItemClick = (id) => (e) => {
console.log('Item clicked:', id);
};
return (
<div>
<button onClick={handleClick}>Click me</button>
<button onClick={handleItemClick(123)}>Item</button>
</div>
);
}
Context API
JSX
import { createContext, useContext, useState } from 'react';
// 1. Create context
const ThemeContext = createContext();
// 2. Create provider
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
const value = {
theme,
toggleTheme
};
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
// 3. Custom hook for consuming context
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// 4. Use in components
function ThemedButton() {
const { theme, toggleTheme } = useTheme();
return (
<button
onClick={toggleTheme}
style={{
background: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff'
}}
>
Toggle Theme
</button>
);
}
// 5. Wrap app with provider
function App() {
return (
<ThemeProvider>
<Header />
<Main />
<ThemedButton />
</ThemeProvider>
);
}
Common Patterns
JSX
// Custom Hook
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// Usage
const [theme, setTheme] = useLocalStorage('theme', 'light');
// Compound Components Pattern
function Tabs({ children, defaultIndex = 0 }) {
const [activeIndex, setActiveIndex] = useState(defaultIndex);
return (
<TabsContext.Provider value={{ activeIndex, setActiveIndex }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
Tabs.List = function TabsList({ children }) {
return <div className="tabs-list">{children}</div>;
};
Tabs.Tab = function Tab({ index, children }) {
const { activeIndex, setActiveIndex } = useContext(TabsContext);
return (
<button
className={activeIndex === index ? 'active' : ''}
onClick={() => setActiveIndex(index)}
>
{children}
</button>
);
};
// Render Props Pattern
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMove);
return () => window.removeEventListener('mousemove', handleMove);
}, []);
return render(position);
}
// Usage
<MouseTracker
render={({ x, y }) => <p>Mouse: {x}, {y}</p>}
/>
Interview Tips:
• Understand the difference between state and props
• Know when to use useCallback vs useMemo
• Understand React's reconciliation algorithm
• Be familiar with React 18 features (Suspense, Transitions)
• Understand the difference between state and props
• Know when to use useCallback vs useMemo
• Understand React's reconciliation algorithm
• Be familiar with React 18 features (Suspense, Transitions)