React & Next.js Topic

React API Integration & Data Fetching

1. React Router Basics

Installation and Setup

bash

npm install react-router-dom

Basic Router Setup

jsx

import React from 'react';
import { 
  BrowserRouter as Router, 
  Routes, 
  Route, 
  Link 
} from 'react-router-dom';

function App() {
  return (
    <Router>
      <div className="app">
        {/* Navigation */}
        <nav>
          <Link to="/">Home</Link>
          <Link to="/about">About</Link>
          <Link to="/users">Users</Link>
          <Link to="/contact">Contact</Link>
        </nav>

        {/* Route Configuration */}
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/users" element={<Users />} />
          <Route path="/contact" element={<Contact />} />
          <Route path="*" element={<NotFound />} />
        </Routes>
      </div>
    </Router>
  );
}

// Basic Page Components
function Home() {
  return (
    <div>
      <h1>Home Page</h1>
      <p>Welcome to our website!</p>
    </div>
  );
}

function About() {
  return (
    <div>
      <h1>About Us</h1>
      <p>Learn more about our company.</p>
    </div>
  );
}

function Users() {
  return (
    <div>
      <h1>Users</h1>
      <p>User management page.</p>
    </div>
  );
}

function Contact() {
  return (
    <div>
      <h1>Contact</h1>
      <p>Get in touch with us.</p>
    </div>
  );
}

function NotFound() {
  return (
    <div>
      <h1>404 - Page Not Found</h1>
      <p>The page you're looking for doesn't exist.</p>
    </div>
  );
}

2. Dynamic Routes and Parameters

URL Parameters

jsx

import { Routes, Route, useParams, Link } from 'react-router-dom';

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/users" element={<UserList />} />
        <Route path="/users/:userId" element={<UserProfile />} />
        <Route path="/products/:category/:id" element={<ProductDetail />} />
      </Routes>
    </Router>
  );
}

function UserList() {
  const users = [
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Smith' },
    { id: 3, name: 'Bob Johnson' }
  ];

  return (
    <div>
      <h1>User List</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            <Link to={`/users/${user.id}`}>
              {user.name}
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

function UserProfile() {
  const { userId } = useParams();
  
  return (
    <div>
      <h1>User Profile</h1>
      <p>Viewing profile for user ID: {userId}</p>
      <Link to="/users">Back to Users</Link>
    </div>
  );
}

function ProductDetail() {
  const { category, id } = useParams();
  
  return (
    <div>
      <h1>Product Details</h1>
      <p>Category: {category}</p>
      <p>Product ID: {id}</p>
    </div>
  );
}

Query Parameters

jsx

import { useSearchParams } from 'react-router-dom';

function SearchPage() {
  const [searchParams, setSearchParams] = useSearchParams();
  const query = searchParams.get('q') || '';
  const category = searchParams.get('category') || 'all';

  const handleSearch = (newQuery, newCategory) => {
    setSearchParams({ 
      q: newQuery, 
      category: newCategory 
    });
  };

  return (
    <div>
      <h1>Search Results</h1>
      <p>Query: {query}</p>
      <p>Category: {category}</p>
      
      <button onClick={() => handleSearch('react', 'books')}>
        Search React Books
      </button>
    </div>
  );
}

3. Navigation Patterns

Programmatic Navigation

jsx

import { useNavigate } from 'react-router-dom';

function LoginForm() {
  const navigate = useNavigate();
  const [formData, setFormData] = useState({ email: '', password: '' });

  const handleSubmit = async (e) => {
    e.preventDefault();
    
    // Simulate login API call
    try {
      await loginUser(formData);
      
      // Redirect after successful login
      navigate('/dashboard', { 
        replace: true,
        state: { from: 'login' }
      });
      
    } catch (error) {
      console.error('Login failed:', error);
    }
  };

  const handleCancel = () => {
    // Go back to previous page
    navigate(-1);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={formData.email}
        onChange={(e) => setFormData({...formData, email: e.target.value})}
        placeholder="Email"
      />
      <input
        type="password"
        value={formData.password}
        onChange={(e) => setFormData({...formData, password: e.target.value})}
        placeholder="Password"
      />
      <button type="submit">Login</button>
      <button type="button" onClick={handleCancel}>Cancel</button>
    </form>
  );
}

// Navigation with state
function ProductList() {
  const navigate = useNavigate();
  
  const viewProduct = (product) => {
    navigate(`/products/${product.id}`, {
      state: { 
        product,
        from: 'product-list',
        search: 'electronics'
      }
    });
  };

  return (
    <div>
      {/* Product list with click handlers */}
    </div>
  );
}

function ProductDetail() {
  const location = useLocation();
  const product = location.state?.product;
  
  if (!product) {
    // Handle case where product is not in state (direct URL access)
    return <div>Product not found</div>;
  }
  
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </div>
  );
}

Active Navigation Links

jsx

import { NavLink, useLocation } from 'react-router-dom';

function Navigation() {
  const location = useLocation();
  
  return (
    <nav className="main-nav">
      <NavLink 
        to="/" 
        className={({ isActive }) => 
          isActive ? 'nav-link active' : 'nav-link'
        }
        end
      >
        Home
      </NavLink>
      
      <NavLink 
        to="/about"
        className={({ isActive }) => 
          isActive ? 'nav-link active' : 'nav-link'
        }
      >
        About
      </NavLink>
      
      <NavLink 
        to="/products"
        className={({ isActive }) => 
          isActive ? 'nav-link active' : 'nav-link'
        }
      >
        Products
      </NavLink>
      
      {/* Custom active logic */}
      <NavLink 
        to="/contact"
        className={({ isActive }) => 
          isActive || location.pathname.startsWith('/contact') 
            ? 'nav-link active' 
            : 'nav-link'
        }
      >
        Contact
      </NavLink>
    </nav>
  );
}

4. Nested Routes

Basic Nested Routes

jsx

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />}>
          <Route index element={<DashboardHome />} />
          <Route path="profile" element={<DashboardProfile />} />
          <Route path="settings" element={<DashboardSettings />} />
          <Route path="analytics" element={<DashboardAnalytics />} />
        </Route>
      </Routes>
    </Router>
  );
}

function Dashboard() {
  return (
    <div className="dashboard">
      <DashboardSidebar />
      <div className="dashboard-content">
        <Outlet /> {/* This is where nested routes render */}
      </div>
    </div>
  );
}

function DashboardSidebar() {
  return (
    <nav className="dashboard-sidebar">
      <NavLink to="/dashboard" end>Overview</NavLink>
      <NavLink to="/dashboard/profile">Profile</NavLink>
      <NavLink to="/dashboard/settings">Settings</NavLink>
      <NavLink to="/dashboard/analytics">Analytics</NavLink>
    </nav>
  );
}

function DashboardHome() {
  return <h2>Dashboard Overview</h2>;
}

function DashboardProfile() {
  return <h2>User Profile</h2>;
}

function DashboardSettings() {
  return <h2>Settings</h2>;
}

function DashboardAnalytics() {
  return <h2>Analytics</h2>;
}

Complex Nested Routes with Parameters

jsx

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/admin" element={<AdminLayout />}>
          <Route index element={<AdminDashboard />} />
          <Route path="users" element={<UsersLayout />}>
            <Route index element={<UserList />} />
            <Route path=":userId" element={<UserDetail />}>
              <Route index element={<UserProfile />} />
              <Route path="edit" element={<EditUser />} />
              <Route path="permissions" element={<UserPermissions />} />
            </Route>
            <Route path="new" element={<NewUser />} />
          </Route>
          <Route path="products/*" element={<ProductsManagement />} />
        </Route>
      </Routes>
    </Router>
  );
}

function AdminLayout() {
  return (
    <div className="admin-layout">
      <AdminHeader />
      <div className="admin-content">
        <Outlet />
      </div>
    </div>
  );
}

function UsersLayout() {
  return (
    <div className="users-layout">
      <h2>User Management</h2>
      <div className="users-content">
        <Outlet />
      </div>
    </div>
  );
}

function UserDetail() {
  const { userId } = useParams();
  
  return (
    <div className="user-detail">
      <h3>User ID: {userId}</h3>
      <nav className="user-subnav">
        <NavLink to={`/admin/users/${userId}`} end>Profile</NavLink>
        <NavLink to={`/admin/users/${userId}/edit`}>Edit</NavLink>
        <NavLink to={`/admin/users/${userId}/permissions`}>Permissions</NavLink>
      </nav>
      <div className="user-detail-content">
        <Outlet />
      </div>
    </div>
  );
}

5. Route Protection and Authentication

Protected Routes

jsx

import { Navigate, useLocation } from 'react-router-dom';

// Auth context (simplified)
const AuthContext = React.createContext();

function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Check for existing auth token
    const token = localStorage.getItem('authToken');
    if (token) {
      // Validate token and set user
      setUser({ name: 'John Doe', role: 'user' });
    }
    setLoading(false);
  }, []);

  const login = (userData) => {
    setUser(userData);
    localStorage.setItem('authToken', 'fake-token');
  };

  const logout = () => {
    setUser(null);
    localStorage.removeItem('authToken');
  };

  return (
    <AuthContext.Provider value={{ user, login, logout, loading }}>
      {children}
    </AuthContext.Provider>
  );
}

function useAuth() {
  const context = React.useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
}

// Protected Route Component
function ProtectedRoute({ children, requiredRole }) {
  const { user, loading } = useAuth();
  const location = useLocation();

  if (loading) {
    return <div>Loading...</div>;
  }

  if (!user) {
    // Redirect to login with return url
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  if (requiredRole && user.role !== requiredRole) {
    return <Navigate to="/unauthorized" replace />;
  }

  return children;
}

// Usage in App
function App() {
  return (
    <AuthProvider>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/login" element={<Login />} />
          <Route path="/public" element={<PublicPage />} />
          
          {/* Protected Routes */}
          <Route 
            path="/dashboard" 
            element={
              <ProtectedRoute>
                <Dashboard />
              </ProtectedRoute>
            } 
          />
          
          <Route 
            path="/admin" 
            element={
              <ProtectedRoute requiredRole="admin">
                <AdminPanel />
              </ProtectedRoute>
            } 
          />
          
          <Route path="/unauthorized" element={<Unauthorized />} />
        </Routes>
      </Router>
    </AuthProvider>
  );
}

6. Advanced Routing Patterns

Lazy Loading with Suspense

jsx

import { lazy, Suspense } from 'react';

const LazyHome = lazy(() => import('./pages/Home'));
const LazyAbout = lazy(() => import('./pages/About'));
const LazyContact = lazy(() => import('./pages/Contact'));

function App() {
  return (
    <Router>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<LazyHome />} />
          <Route path="/about" element={<LazyAbout />} />
          <Route path="/contact" element={<LazyContact />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

Route Configuration Object

jsx

// routes.js
export const routes = [
  {
    path: '/',
    element: <Home />,
    title: 'Home',
    exact: true
  },
  {
    path: '/about',
    element: <About />,
    title: 'About Us'
  },
  {
    path: '/products',
    element: <Products />,
    title: 'Products',
    children: [
      {
        path: ':category',
        element: <ProductCategory />
      },
      {
        path: ':category/:id',
        element: <ProductDetail />
      }
    ]
  }
];

// App.js
import { routes } from './routes';

function renderRoutes(routes) {
  return routes.map((route, index) => (
    <Route
      key={index}
      path={route.path}
      element={route.element}
      exact={route.exact}
    >
      {route.children && renderRoutes(route.children)}
    </Route>
  ));
}

function App() {
  return (
    <Router>
      <Routes>
        {renderRoutes(routes)}
      </Routes>
    </Router>
  );
}

Custom Navigation Hook

jsx

import { useNavigate, useLocation } from 'react-router-dom';

function useNavigation() {
  const navigate = useNavigate();
  const location = useLocation();

  const goTo = (path, options = {}) => {
    navigate(path, options);
  };

  const goBack = () => {
    navigate(-1);
  };

  const goForward = () => {
    navigate(1);
  };

  const replace = (path, state) => {
    navigate(path, { replace: true, state });
  };

  const getQueryParam = (param) => {
    const searchParams = new URLSearchParams(location.search);
    return searchParams.get(param);
  };

  const setQueryParam = (params) => {
    const searchParams = new URLSearchParams(location.search);
    
    Object.entries(params).forEach(([key, value]) => {
      if (value === null || value === undefined) {
        searchParams.delete(key);
      } else {
        searchParams.set(key, value);
      }
    });

    navigate(`${location.pathname}?${searchParams.toString()}`, {
      replace: true
    });
  };

  return {
    goTo,
    goBack,
    goForward,
    replace,
    getQueryParam,
    setQueryParam,
    currentPath: location.pathname,
    state: location.state
  };
}

// Usage
function MyComponent() {
  const navigation = useNavigation();

  const handleSearch = (query) => {
    navigation.setQueryParam({ q: query, page: 1 });
  };

  return (
    <div>
      <button onClick={() => navigation.goTo('/dashboard')}>
        Go to Dashboard
      </button>
      <button onClick={() => navigation.replace('/login')}>
        Replace with Login
      </button>
    </div>
  );
}

7. Error Boundaries with Routes

jsx

import { ErrorBoundary } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <h2>Something went wrong:</h2>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      onReset={() => window.location.reload()}
    >
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          {/* Routes with their own error boundaries */}
          <Route 
            path="/dashboard/*" 
            element={
              <ErrorBoundary fallback={<div>Dashboard Error</div>}>
                <DashboardRoutes />
              </ErrorBoundary>
            } 
          />
        </Routes>
      </Router>
    </ErrorBoundary>
  );
}

This comprehensive routing guide covers everything from basic setup to advanced patterns like nested routes, authentication, and performance optimization with lazy loading.

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?