16 KiB
Advanced React & State Management
Complete Course Guide | 20 Hours | Advanced Level
Table of Contents
- Chapter 1: React Fundamentals Review
- Chapter 2: Hooks Deep Dive
- Chapter 3: Custom Hooks
- Chapter 4: Context API & Global State
- Chapter 5: Redux Mastery
- Chapter 6: Performance Optimization
- Chapter 7: Advanced Patterns
- Chapter 8: Testing React
- Chapter 9: TypeScript with React
- Chapter 10: Real-World Application
Chapter 1: React Fundamentals Review
Components
React applications are built from components.
Function Components
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// With destructuring
function Welcome({ name, email }) {
return (
<div>
<h1>Hello, {name}</h1>
<p>Email: {email}</p>
</div>
);
}
Class Components
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
Function components are preferred in modern React (use Hooks instead of class lifecycle).
JSX
JSX looks like HTML but is JavaScript:
// JSX
const element = <h1>Hello, World!</h1>;
// Compiles to:
const element = React.createElement('h1', null, 'Hello, World!');
Props vs State
Props
- Read-only data passed from parent to child
- Cannot be modified by the child
- Use for passing configuration
<User name="John" age={30} />
State
- Mutable data owned by a component
- Can be updated with setState (or setState Hook)
- Causes re-render when changed
Virtual DOM
React's efficiency secret:
- Update Virtual DOM (fast, in-memory)
- Diff with previous Virtual DOM
- Update actual DOM (slow operation) only where needed
- Re-render component
Chapter 2: Hooks Deep Dive
useState
Manage component state:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
Multiple state values:
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [age, setAge] = useState(null);
State batching (React 18+):
function handleClick() {
// Both updates are batched
setCount(c => c + 1);
setFlagStatus(f => !f);
// Only one re-render!
}
useEffect
Handle side effects:
useEffect(() => {
// This runs after every render
console.log('Component rendered');
});
useEffect(() => {
// This runs once on mount
console.log('Component mounted');
}, []); // Empty dependency array
useEffect(() => {
// This runs when deps change
console.log('Dependencies changed');
}, [dependency1, dependency2]);
Cleanup function:
useEffect(() => {
const subscription = data.subscribe();
return () => {
// Cleanup on unmount
subscription.unsubscribe();
};
}, []);
useReducer
Complex state management:
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:
return state;
}
}
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>
);
}
useContext
Access context without nesting:
// Create context
const ThemeContext = React.createContext('light');
// Provide
<ThemeContext.Provider value="dark">
<MyComponent />
</ThemeContext.Provider>
// Consume
function MyComponent() {
const theme = useContext(ThemeContext);
return <div style={{ background: theme }}>Content</div>;
}
Other Important Hooks
useCallback
Memoize function references:
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
useMemo
Memoize expensive computations:
const memoizedValue = useMemo(() => {
return expensiveComputation(a, b);
}, [a, b]);
useRef
Access DOM directly:
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<>
<input ref={inputRef} />
<button onClick={focusInput}>Focus</button>
</>
);
}
Chapter 3: Custom Hooks
Creating Custom Hooks
Extract component logic into reusable hooks:
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.log(error);
return initialValue;
}
});
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.log(error);
}
};
return [storedValue, setValue];
}
// Usage
function MyComponent() {
const [name, setName] = useLocalStorage('name', '');
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
);
}
useAsync
Handle async operations:
function useAsync(asyncFunction, immediate = true) {
const [status, setStatus] = useState('idle');
const [value, setValue] = useState(null);
const [error, setError] = useState(null);
const execute = useCallback(async () => {
setStatus('pending');
setValue(null);
setError(null);
try {
const response = await asyncFunction();
setValue(response);
setStatus('success');
return response;
} catch (error) {
setError(error);
setStatus('error');
}
}, [asyncFunction]);
useEffect(() => {
if (immediate) {
execute();
}
}, [execute, immediate]);
return { execute, status, value, error };
}
// Usage
function FetchUser({ userId }) {
const { status, value, error } = useAsync(
() => fetch(`/api/user/${userId}`).then(r => r.json())
);
if (status === 'pending') return <div>Loading...</div>;
if (status === 'error') return <div>Error: {error.message}</div>;
if (status === 'success') return <div>{value.name}</div>;
}
useFetch
Dedicated hook for fetching data:
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
fetch(url)
.then((res) => res.json())
.then((data) => {
if (!cancelled) {
setData(data);
setLoading(false);
}
})
.catch((err) => {
if (!cancelled) {
setError(err);
setLoading(false);
}
});
return () => {
cancelled = true;
};
}, [url]);
return { data, loading, error };
}
Chapter 4: Context API & Global State
When to Use Context
- Theme data (light/dark mode)
- User authentication state
- UI preferences
- Language/localization
Context Provider Pattern
// Create context
const AppContext = createContext();
// Create provider component
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [notifications, setNotifications] = useState([]);
const value = {
user,
setUser,
notifications,
setNotifications,
};
return (
<AppContext.Provider value={value}>
{children}
</AppContext.Provider>
);
}
// Custom hook for using context
function useApp() {
const context = useContext(AppContext);
if (!context) {
throw new Error('useApp must be used within AppProvider');
}
return context;
}
// Usage
<AppProvider>
<MyApp />
</AppProvider>
function MyComponent() {
const { user, setUser } = useApp();
// ...
}
Multiple Contexts
Combine multiple contexts:
function Root({ children }) {
return (
<ThemeProvider>
<AuthProvider>
<NotificationProvider>
{children}
</NotificationProvider>
</AuthProvider>
</ThemeProvider>
);
}
Context Optimization
Context causes re-render of all consumers. Optimize by:
// Split into smaller contexts
const UserContext = createContext();
const UIContext = createContext();
// Memoize provider value
const value = useMemo(() => ({
user,
setUser
}), [user]);
return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
);
Chapter 5: Redux Mastery
Redux Concepts
Store
Single source of truth containing all application state:
const store = createStore(reducer, initialState);
Actions
Plain objects describing what happened:
const action = {
type: 'USER_LOGGED_IN',
payload: { userId: 123, name: 'John' }
};
Reducers
Pure functions that return new state:
function userReducer(state = null, action) {
switch(action.type) {
case 'USER_LOGGED_IN':
return action.payload;
case 'USER_LOGGED_OUT':
return null;
default:
return state;
}
}
Redux with React
import { useSelector, useDispatch } from 'react-redux';
function MyComponent() {
// Subscribe to state
const user = useSelector(state => state.user);
// Dispatch actions
const dispatch = useDispatch();
return (
<div>
<p>User: {user?.name}</p>
<button onClick={() => dispatch({ type: 'USER_LOGGED_OUT' })}>
Logout
</button>
</div>
);
}
Redux Toolkit (Modern Redux)
import { createSlice, configureStore } from '@reduxjs/toolkit';
// Create slice (reducer + actions)
const userSlice = createSlice({
name: 'user',
initialState: null,
reducers: {
loginUser: (state, action) => action.payload,
logoutUser: () => null,
},
});
// Create store
const store = configureStore({
reducer: {
user: userSlice.reducer,
},
});
// Export actions
export const { loginUser, logoutUser } = userSlice.actions;
// Usage
function MyComponent() {
const user = useSelector(state => state.user);
const dispatch = useDispatch();
return (
<button onClick={() => dispatch(logoutUser())}>
Logout
</button>
);
}
Chapter 6: Performance Optimization
React.memo
Prevent unnecessary re-renders:
const MyComponent = React.memo(function MyComponent(props) {
return <div>{props.value}</div>;
});
// Only re-renders if props change
useMemo
Memoize expensive computations:
function ExpensiveComponent({ items }) {
const sortedItems = useMemo(() => {
console.log('Sorting...');
return items.sort((a, b) => a.value - b.value);
}, [items]);
return <div>{sortedItems.length} items</div>;
}
useCallback
Prevent child re-renders:
function Parent() {
const [count, setCount] = useState(0);
// Without useCallback, Child re-renders every time
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
return <Child onClick={handleClick} />;
}
const Child = React.memo(({ onClick }) => (
<button onClick={onClick}>Click me</button>
));
Code Splitting
Load code on demand:
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
Profiling
Measure performance:
import { Profiler } from 'react';
function onRenderCallback(id, phase, actualDuration) {
console.log(`${id} (${phase}) took ${actualDuration}ms`);
}
<Profiler id="MyComponent" onRender={onRenderCallback}>
<MyComponent />
</Profiler>
Chapter 7: Advanced Patterns
Render Props Pattern
function DataProvider({ children }) {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, []);
return children({ data });
}
// Usage
<DataProvider>
{({ data }) => <div>{data?.title}</div>}
</DataProvider>
Higher-Order Components (HOC)
function withAuth(Component) {
return function ProtectedComponent(props) {
const { user } = useContext(AuthContext);
if (!user) return <Navigate to="/login" />;
return <Component {...props} />;
};
}
const ProtectedPage = withAuth(MyPage);
Compound Components
function Form({ children }) {
const [values, setValues] = useState({});
return <FormContext.Provider value={values}>{children}</FormContext.Provider>;
}
Form.Input = function FormInput({ name }) {
const values = useContext(FormContext);
return <input value={values[name]} />;
};
// Usage
<Form>
<Form.Input name="email" />
<Form.Input name="password" />
</Form>
Chapter 8: Testing React
Unit Testing with Jest
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';
test('renders welcome message', () => {
render(<MyComponent />);
expect(screen.getByText('Welcome')).toBeInTheDocument();
});
test('button click handler called', () => {
const handleClick = jest.fn();
render(<button onClick={handleClick}>Click</button>);
screen.getByRole('button').click();
expect(handleClick).toHaveBeenCalled();
});
Integration Testing
test('user login flow', async () => {
render(<LoginForm />);
const emailInput = screen.getByLabelText('Email');
const passwordInput = screen.getByLabelText('Password');
const submitButton = screen.getByText('Login');
userEvent.type(emailInput, 'test@example.com');
userEvent.type(passwordInput, 'password');
userEvent.click(submitButton);
await waitFor(() => {
expect(screen.getByText('Welcome')).toBeInTheDocument();
});
});
Chapter 9: TypeScript with React
Basic Types
interface User {
id: number;
name: string;
email: string;
}
interface Props {
user: User;
onUpdate: (user: User) => void;
}
function UserCard({ user, onUpdate }: Props) {
return <div>{user.name}</div>;
}
Component Types
// Function component
type MyComponentProps = {
title: string;
count?: number;
};
const MyComponent: React.FC<MyComponentProps> = ({ title, count = 0 }) => {
return <div>{title}: {count}</div>;
};
// With children
interface Props {
children: React.ReactNode;
}
function Container({ children }: Props) {
return <div>{children}</div>;
}
Chapter 10: Real-World Application
Build a complete todo application with:
- Multiple components
- State management
- API integration
- Local storage
- Error handling
- Loading states
This ties everything together into a production-ready application.
Summary & Resources
You've learned advanced React patterns and optimization techniques. Key takeaways:
- Hooks: useState, useEffect, useContext, useReducer
- State Management: Context API, Redux
- Performance: Memoization, code splitting, profiling
- Testing: Jest, React Testing Library
- TypeScript: Type safety for React applications
Next Steps
- Build production applications
- Contribute to open source
- Learn Next.js for full-stack development
- Explore React Native for mobile
Happy coding! ⚛️