Routing

React Router v7 Documentation

SmartMaker uses React Router to manage navigation between application pages (Single Page Application).

Basic Configuration

Create the Router

// src/components/app/Router/index.jsx

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

import { LoginPage } from '../../pages/public/LoginPage';
import { HomePage } from '../../pages/private/HomePage';
import { Error404Page } from '../../pages/errors/Error404Page';

export const Router = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/login" element={<LoginPage />} />
        <Route path="/" element={<HomePage />} />
        <Route path="*" element={<Error404Page />} />
      </Routes>
    </BrowserRouter>
  );
};

Integrate into App.jsx

// src/App.jsx

import { Provider } from '@cap-rel/smartcommon';
import { Router } from './components/app/Router';
import { config } from './appConfig';

export const App = () => {
  return (
    <Provider config={config}>
      <Router />
    </Provider>
  );
};

Protected Routes

Tip

With smartcommon, use <RouteGuard>: it covers 4 modes (requireAuth, requireGuest, requireDeviceIdentification, requireDeviceIdentified) and directly reads useApi().user / user.deviceOptions. See Advanced Components -> RouteGuard for the full version. The manual version below remains useful pedagogically or for business rules that do not fit these 4 modes.

import { RouteGuard } from '@cap-rel/smartcommon';

<Routes>
  <Route element={<RouteGuard requireGuest />}>
    <Route path="/login" element={<LoginPage />} />
    <Route path="/welcome" element={<WelcomePage />} />
  </Route>

  <Route element={<RouteGuard requireDeviceIdentified />}>
    <Route path="/" element={<HomePage />} />
    <Route path="/settings" element={<SettingsPage />} />
  </Route>
</Routes>

Authentication Guards (Manual)

// src/components/app/Router/Guards/index.jsx

import { Outlet, Navigate } from 'react-router-dom';
import { useGlobalStates } from '@cap-rel/smartcommon';

/**
 * Public routes (login, register, etc.)
 * Redirects to / if already logged in
 */
export const PublicRoutes = () => {
  const gst = useGlobalStates();
  const session = gst.get('session');

  if (session) {
    return <Navigate to="/" replace />;
  }

  return <Outlet />;
};

/**
 * Private routes (home, settings, etc.)
 * Redirects to /login if not authenticated
 */
export const PrivateRoutes = () => {
  const gst = useGlobalStates();
  const session = gst.get('session');

  if (!session) {
    return <Navigate to="/login" replace />;
  }

  return <Outlet />;
};

Using Guards

// src/components/app/Router/index.jsx

import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { PublicRoutes, PrivateRoutes } from './Guards';

// Public pages
import { WelcomePage } from '../../pages/public/WelcomePage';
import { LoginPage } from '../../pages/public/LoginPage';

// Private pages
import { HomePage } from '../../pages/private/HomePage';
import { SettingsPage } from '../../pages/private/SettingsPage';
import { ItemPage } from '../../pages/private/ItemPage';

// Errors
import { Error404Page } from '../../pages/errors/Error404Page';

export const Router = () => {
  return (
    <BrowserRouter>
      <Routes>
        {/* Public routes */}
        <Route element={<PublicRoutes />}>
          <Route path="/welcome" element={<WelcomePage />} />
          <Route path="/login" element={<LoginPage />} />
        </Route>

        {/* Private routes */}
        <Route element={<PrivateRoutes />}>
          <Route path="/" element={<HomePage />} />
          <Route path="/settings" element={<SettingsPage />} />
          <Route path="/items/:id" element={<ItemPage />} />
        </Route>

        {/* Fallback 404 */}
        <Route path="*" element={<Error404Page />} />
      </Routes>
    </BrowserRouter>
  );
};

useNavigation Hook

SmartCommon exposes useNavigation which returns an object with navigation methods:

import { useNavigation } from '@cap-rel/smartcommon';

const MyComponent = () => {
  const nav = useNavigation();

  return (
    <div>
      {/* Simple navigation */}
      <button onClick={() => nav.navigate('/')}>Home</button>
      <button onClick={() => nav.navigate('/settings')}>Settings</button>

      {/* With parameters */}
      <button onClick={() => nav.navigate(`/items/${itemId}`)}>View item</button>

      {/* Go back */}
      <button onClick={() => nav.navigate(-1)}>Back</button>

      {/* Replace history (chainable builder) */}
      <button onClick={() => nav.replace().to('/login')}>
        Logout
      </button>
    </div>
  );
};

Returned Properties

Property Description
nav.navigate(to, options) React Router navigation function
nav.params Route parameters (useParams)
nav.searchParams Query string (useSearchParams)
nav.location Current location object
nav.history Navigation history
nav.replace() Chainable builder: replace instead of push
nav.state(value) Chainable builder: pass a state
nav.to(path) Chainable builder: execute navigation

For simple links, use the Link component:

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

const Navigation = () => {
  return (
    <nav>
      <Link to="/">Home</Link>
      <Link to="/settings">Settings</Link>
      <Link to="/items/123">Item 123</Link>
    </nav>
  );
};

Route Parameters

Dynamic Parameters

// Route with parameter :id
<Route path="/items/:id" element={<ItemPage />} />

// Get the parameter
import { useParams } from 'react-router-dom';

const ItemPage = () => {
  const { id } = useParams();  // id = "123" for /items/123

  return <div>Item #{id}</div>;
};

Multiple Parameters

// Route with multiple parameters
<Route path="/users/:userId/posts/:postId" element={<PostPage />} />

const PostPage = () => {
  const { userId, postId } = useParams();
  // ...
};

Query Strings

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

const SearchPage = () => {
  const [searchParams, setSearchParams] = useSearchParams();

  // Read: /search?q=test&page=2
  const query = searchParams.get('q');     // "test"
  const page = searchParams.get('page');   // "2"

  // Modify
  const handleSearch = (newQuery) => {
    setSearchParams({ q: newQuery, page: '1' });
  };

  return (
    <input
      value={query || ''}
      onChange={(e) => handleSearch(e.target.value)}
    />
  );
};

Nested Routes

Shared Layout

// src/components/app/Router/index.jsx

<Routes>
  <Route element={<PrivateRoutes />}>
    {/* Layout with bottom navigation */}
    <Route element={<MainLayout />}>
      <Route path="/" element={<HomePage />} />
      <Route path="/search" element={<SearchPage />} />
      <Route path="/profile" element={<ProfilePage />} />
    </Route>

    {/* Pages without bottom navigation */}
    <Route path="/items/:id" element={<ItemPage />} />
    <Route path="/settings" element={<SettingsPage />} />
  </Route>
</Routes>

Layout Component

// src/components/layouts/MainLayout/index.jsx

import { Outlet } from 'react-router-dom';
import { useNavigation } from '@cap-rel/smartcommon';

export const MainLayout = () => {
  const nav = useNavigation();

  return (
    <div className="min-h-screen flex flex-col">
      {/* Page content (Outlet = route child) */}
      <main className="flex-1">
        <Outlet />
      </main>

      {/* Fixed bottom navigation */}
      <nav className="bg-white shadow-lg p-2 flex justify-around">
        <button onClick={() => nav.navigate('/')}>Home</button>
        <button onClick={() => nav.navigate('/search')}>Search</button>
        <button onClick={() => nav.navigate('/profile')}>Profile</button>
      </nav>
    </div>
  );
};

Transition Animations

Configuration in appConfig

// appConfig.js

export const config = {
  pages: {
    // From home
    "/": {
      "/settings": "slideLeft",   // Home -> Settings: slide left
      "/items/*": "slideLeft",    // Home -> Item: slide left
      "*": "fade",                // Others: fade
    },
    // From settings
    "/settings": {
      "/": "slideRight",          // Settings -> Home: slide right
    },
    // Default
    "*": "fade",
  },
};

Available Animations

Animation Description
fade Crossfade
slideLeft Slide left
slideRight Slide right
zoom Zoom in/out

See Animations for more details.

Error Handling

404 Page

// src/components/pages/errors/Error404Page/index.jsx

import { useNavigation } from '@cap-rel/smartcommon';

export const Error404Page = () => {
  const nav = useNavigation();

  return (
    <div className="min-h-screen flex flex-col items-center justify-center p-4">
      <h1 className="text-6xl font-bold text-gray-300 mb-4">404</h1>
      <p className="text-gray-600 mb-8">Page not found</p>
      <button
        onClick={() => nav.navigate('/')}
        className="bg-primary text-white px-6 py-3 rounded-lg"
      >
        Back to Home
      </button>
    </div>
  );
};

Conditional Redirect

import { Navigate, useLocation } from 'react-router-dom';
import { useGlobalStates } from '@cap-rel/smartcommon';

const RequireAuth = ({ children }) => {
  const gst = useGlobalStates();
  const session = gst.get('session');
  const location = useLocation();

  if (!session) {
    // Save URL to redirect after login
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
};

Best Practices

Route Structure

  • Group routes by type (public, private, errors)
  • Use guards for protection
  • Place the * fallback last
  • Prefer SmartCommon's useNavigation
  • Use replace: true for redirects (login, logout)
  • Avoid hardcoded paths, use constants

Performance

  • Lazy loading for large pages
  • Light animations on mobile
  • Preload critical data

See Also