Routing
React Router
A library to handle navigation inside your website
Works very similarly to React Native routing library, React Navigation
Folder Structure
src/
├─ pages/
│ ├─ HTML/
│ │ ├─ Basics.js
│ │ ├─ Forms.js
│ ├─ CSS/
│ │ ├─ Basics.js
│ │ ├─ Properties.js
├─ App.js
├─ index.jsWrap BrowserRouter around the root component
// JSX
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);Set up the Routes and Link elements
Routesacts as the main-content container and will swap out components based on URLLinkreplaces element<a>
// JSX
import { Routes, Route, Link } from 'react-router-dom';
import HTMLBasics from './pages/HTML/Basics';
import Forms from './pages/HTML/Forms';
import CSSBasics from './pages/CSS/Basics';
import CSSProperties from './pages/CSS/Properties';
export default function App() {
return (
<div>
{/* Sidebar Navigation */}
<nav>
<h3>HTML</h3>
<ul>
<li><Link to='/html/basics'>HTML Basics</Link></li>
<li><Link to='/html/forms'>Forms</Link></li>
</ul>
<h3>CSS</h3>
<ul>
<li><Link to='/css/basics'>CSS Basics</Link></li>
<li><Link to='/css/properties'>CSS Properties</Link></li>
</ul>
</nav>
{/* Page Content */}
<main>
<Routes>
{/* HTML Routes */}
<Route path='/html/basics' element={<HTMLBasics />} />
<Route path='/html/forms' element={<Forms />} />
{/* CSS Routes */}
<Route path='/css/basics' element={<CSSBasics />} />
<Route path='/css/properties' element={<CSSProperties />} />
</Routes>
</main>
</div>
);
}State Management
Zustand
Quick and lightweight way to manage state to avoid props drilling and allow multiple components to share states
- Can store ref, values, objects, functions and avoid props drilling
- Minimal boilerplate code
- Supports async actions naturally
- Can set up small, modular stores
- Works the same way in React Native
- When updating the store, Zustand does a shallow merge
- If updating nested objects, keys not in the new object will be deleted
- Zustand Recommendation: zustand.docs.pmnd.rs/guides/updating-state
Code sample to manage a store with 2 states: theme and user
Setting up the store
// JSX
// store.js
import { create } from 'zustand';
export const useStore = create((set) => ({
theme: 'light',
user: { name: '', email: '' },
// Theme actions
toggleTheme: () =>
set((currentState) => ({
theme: currentState.theme === 'light' ? 'dark' : 'light',
})),
// User actions
setUser: (newUser) => set({ user: newUser }),
clearUser: () => set({ user: { name: '', email: '' } }),
}));Updating the store and getting value
// JSX
// App.jsx
import React from 'react';
import { useStore } from './store';
export default function App() {
const { theme, toggleTheme, user, setUser, clearUser } = useStore();
return (
<div>
<h1>Zustand Example</h1>
<p>Current theme: {theme}</p>
<button onClick={toggleTheme}>Toggle Theme</button>
<h2>User Profile</h2>
{user.name ? (
<div>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
<button onClick={clearUser}>Clear User</button>
</div>
) : (
<button onClick={() => setUser({ name: 'Tran', email: 'tran@example.com' })}>
Set User
</button>
)}
</div>
);
}Redux
Centralized state management for your entire app.
- Uses a single global store
- Require high boilerplate code
- Higher learning curve to understand
- Works the same way in React Native
Code sample to manage a store with 2 states: theme and user
Setting up the store
// JSX
// store.js
import { configureStore, createSlice } from '@reduxjs/toolkit';
// Theme slice
const themeSlice = createSlice({
name: 'theme',
initialState: { mode: 'light' },
reducers: {
toggleTheme: (state) => {
state.mode = state.mode === 'light' ? 'dark' : 'light';
},
},
});
// User slice
const userSlice = createSlice({
name: 'user',
initialState: { name: '', email: '' },
reducers: {
setUser: (state, action) => {
state.name = action.payload.name;
state.email = action.payload.email;
},
clearUser: (state) => {
state.name = '';
state.email = '';
},
},
});
export const { toggleTheme } = themeSlice.actions;
export const { setUser, clearUser } = userSlice.actions;
export const store = configureStore({
reducer: {
theme: themeSlice.reducer,
user: userSlice.reducer,
},
});Using the store and getting value
// JSX
// App.jsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { toggleTheme, setUser, clearUser } from './store';
function App() {
const dispatch = useDispatch();
const theme = useSelector((state) => state.theme.mode);
const user = useSelector((state) => state.user);
return (
<div>
<h1>Redux Example</h1>
<p>Current theme: {theme}</p>
<button onClick={() => dispatch(toggleTheme())}>Toggle Theme</button>
<h2>User Profile</h2>
{user.name ? (
<div>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
<button onClick={() => dispatch(clearUser())}>Clear User</button>
</div>
) : (
<button onClick={() => dispatch(setUser({ name: 'Tran', email: 'tran@example.com' }))}>
Set User
</button>
)}
</div>
);
}Data Fetch & Server-State Management
React Query (TanStack Query)
React Query is a powerful library for data fetching and managing server-side state. It helps you:
- Fetch, cache, and update server data efficiently
- Auto-refetch when data changes or the user focuses the window
- Simplifies managing loading, success, and error states
- Works seamlessly with REST APIs and GraphQL
Installation
# Install React Query
npm install @tanstack/react-querySetup the QueryClientProvider
// JSX
// main.jsx or index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import App from './App';
const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById('root')).render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);Fetching data with useQuery
// JSX
// App.jsx
import React from 'react';
import { useQuery } from '@tanstack/react-query';
async function fetchUsers() {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
}
export default function App() {
const { data, isLoading, isError, error, refetch } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 5000, // Data stays fresh for 5 seconds
});
if (isLoading) return <p>Loading users...</p>;
if (isError) return <p>Error: {error.message}</p>;
return (
<div>
<h1>React Query Example</h1>
<button onClick={refetch}>Refetch Users</button>
<ul>
{data.map((user) => (
<li key={user.id}>
{user.name} — {user.email}
</li>
))}
</ul>
</div>
);
}Posting data with useMutation
// JSX
// CreateUser.jsx
import React, { useState } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
async function createUser(newUser) {
const response = await fetch('https://jsonplaceholder.typicode.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newUser),
});
if (!response.ok) {
throw new Error('Failed to create user');
}
return response.json();
}
export default function CreateUser() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const queryClient = useQueryClient();
const { mutate, isPending, isError, error } = useMutation({
mutationFn: createUser,
onSuccess: (data) => {
// Invalidate the users cache to refetch updated data
queryClient.invalidateQueries({ queryKey: ['users'] });
alert(`User ${data.name} created successfully!`);
},
});
const handleSubmit = (e) => {
e.preventDefault();
mutate({ name, email });
};
return (
<form onSubmit={handleSubmit}>
<h2>Create User</h2>
<input
type='text'
placeholder='Name'
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
<input
type='email'
placeholder='Email'
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<button type='submit' disabled={isPending}>
{isPending ? 'Creating...' : 'Create User'}
</button>
{isError && <p style={{ color: 'red' }}>Error: {error.message}</p>}
</form>
);
}Automatic refetching
- React Query automatically refetches data when:
- The window regains focus
- The network reconnects
- The query becomes stale
Summary
useQuery→ Fetch and cache datauseMutation→ Create, update, or delete datainvalidateQueries→ Refresh cache manuallystaleTimeandcacheTime→ Control caching