---
source_hash: "5a9f69c5"
title: "React Development (frontend)"
weight: 250
---

# React Development (frontend)

You just deployed SmartBoot in your module and wonder where to start? You're in the right place!

## Install Dependencies

```
cd mobile
npm i
```

## Initial Configuration

### .env File

Copy or rename `mobile/.env.example` to `mobile/.env`:

```
VITE_API_URL=https://your-dolibarr.com/custom/monmodule/pwa/api.php
VITE_APP_VERSION=dev
VITE_LOCALES=en,fr
```

### Application Configuration

The `appConfig.js` file centralizes all configuration:

```js
// src/appConfig.js

export const config = {
  // Debug mode (colored logs in console)
  debug: import.meta.env.DEV,

  // API Configuration
  api: {
    prefixUrl: import.meta.env.VITE_API_URL,
    timeout: 30000,
    paths: {
      login: "login",
      logout: "logout",
      refresh: "refresh",
    },
  },

  // Storage
  storage: {
    local: ["session", "settings"],  // Persisted in localStorage
  },

  // Initial global state
  globalState: {
    reducers: {
      session: null,
      settings: { lng: "fr" },
      items: [],
    },
  },

  // Page animations
  pages: {
    "/": { "/settings": "slideLeft", "*": "fade" },
    "*": "fade",
  },
};
```

## Launch

```
cd mobile
npm run dev
```

Open http://localhost:5173/ (mobile mode recommended in DevTools).

> [!IMPORTANT]
> [SmartAuth](/howto/smartauth) is required!

## Page Structure

The tree structure pre-installed by SmartBoot:

```
src/components/pages/
├── errors/
│   └── Error404Page/
│       └── index.jsx
├── private/
│   └── HomePage/
│       └── index.jsx
└── public/
    ├── LoginPage/
    │   └── index.jsx
    └── WelcomePage/
        └── index.jsx
```

| Directory | Description |
| --- | --- |
| `public/` | Pages accessible without authentication |
| `private/` | Pages requiring authentication |
| `errors/` | Error pages (404, etc.) |

## Create a Login Page

> [!TIP]
> The SmartBoot skeleton now uses `<LoginComponent>` from smartcommon, which provides the complete form **and the QR scan pairing with smartAuth**. See [Advanced Components -> LoginComponent](/front/composants-avances#logincomponent). The example below is a manual setup useful if you want to understand the flow or write a very specific page.

With SmartCommon, the login page becomes very simple:

```jsx
// src/components/pages/public/LoginPage/index.jsx

import { useApi, useGlobalStates, useForm, useNavigation } from '@cap-rel/smartcommon';
import { Form, Input, Button } from '@cap-rel/smartcommon';

export const LoginPage = () => {
  const api = useApi();
  const nav = useNavigation();
  const gst = useGlobalStates();

  const form = useForm({ defaultValues: { login: '', password: '' } });

  const handleSubmit = async (data) => {
    try {
      const user = await api.login(data);
      gst.local.set('session', user);
      nav.navigate('/');
    } catch (error) {
      console.error('Login failed:', error);
    }
  };

  return (
    <div className="fixed inset-0 bg-white flex justify-center items-center p-10">
      <Form form={form} onSubmit={handleSubmit} className="flex flex-col gap-6 w-full max-w-sm">
        <Input
          name="login"
          label="Username"
          placeholder="Your login..."
        />
        <Input
          name="password"
          type="password"
          label="Password"
          placeholder="●●●●●●●●"
        />
        <Button type="submit" loading={form.isFormSubmitting}>
          Login
        </Button>
      </Form>
    </div>
  );
};
```

## Create a Private Page

### Home Page with List

```jsx
// src/components/pages/private/HomePage/index.jsx

import { useApi, useGlobalStates, useNavigation } from '@cap-rel/smartcommon';
import { useEffect } from 'react';

export const HomePage = () => {
  const api = useApi();
  const nav = useNavigation();
  const gst = useGlobalStates();
  const items = gst.get('items') ?? [];

  // Load items on mount
  useEffect(() => {
    const fetchItems = async () => {
      try {
        const data = await api.get('items');
        gst.set('items', data);
      } catch (error) {
        console.error('Fetch failed:', error);
      }
    };
    fetchItems();
  }, []);

  // Logout
  const handleLogout = async () => {
    await api.logout();
    nav.navigate('/login');
  };

  return (
    <div className="min-h-screen bg-gray-100 p-4">
      <header className="flex justify-between items-center mb-6">
        <h1 className="text-2xl font-bold">My items</h1>
        <button onClick={handleLogout} className="text-red-500">
          Logout
        </button>
      </header>

      <div className="space-y-4">
        {items.map((item) => (
          <div
            key={item.id}
            onClick={() => nav.navigate(`/items/${item.id}`)}
            className="bg-white p-4 rounded-lg shadow"
          >
            <h2 className="font-semibold">{item.label}</h2>
            <p className="text-gray-600">{item.description}</p>
          </div>
        ))}
      </div>
    </div>
  );
};
```

### Detail Page

```jsx
// src/components/pages/private/ItemPage/index.jsx

import { useApi, useGlobalStates, useNavigation } from '@cap-rel/smartcommon';
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';

export const ItemPage = () => {
  const { id } = useParams();
  const api = useApi();
  const nav = useNavigation();
  const [item, setItem] = useState(null);

  useEffect(() => {
    const fetchItem = async () => {
      try {
        const data = await api.get(`items/${id}`);
        setItem(data);
      } catch (error) {
        console.error('Fetch failed:', error);
      }
    };
    fetchItem();
  }, [id]);

  if (!item) {
    return <div className="p-4">Loading...</div>;
  }

  return (
    <div className="min-h-screen bg-gray-100">
      <header className="bg-white p-4 shadow">
        <button onClick={() => nav.navigate(-1)} className="text-blue-500">
          ← Back
        </button>
      </header>

      <div className="p-4">
        <h1 className="text-2xl font-bold mb-4">{item.label}</h1>
        <p className="text-gray-600">{item.description}</p>
      </div>
    </div>
  );
};
```

## Configure the Router

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

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

import { LoginPage } from '../../pages/public/LoginPage';
import { WelcomePage } from '../../pages/public/WelcomePage';
import { HomePage } from '../../pages/private/HomePage';
import { ItemPage } from '../../pages/private/ItemPage';
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="/items/:id" element={<ItemPage />} />
        </Route>

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

### Guards with SmartCommon

> [!TIP]
> For apps from the SmartBoot skeleton, prefer `<RouteGuard>` which covers 4 modes (auth + device identification) and is maintained in smartcommon. See [RouteGuard](/front/composants-avances#routeguard). The manual version below illustrates the underlying pattern.

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

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

export const PublicRoutes = () => {
  const gst = useGlobalStates();
  const session = gst.get('session');
  return session ? <Navigate to="/" /> : <Outlet />;
};

export const PrivateRoutes = () => {
  const gst = useGlobalStates();
  const session = gst.get('session');
  return session ? <Outlet /> : <Navigate to="/login" />;
};
```

## Use Global States

### Store Data

```jsx
import { useGlobalStates } from '@cap-rel/smartcommon';

const gst = useGlobalStates();

// Read a value
const items = gst.get('items');
const lng = gst.get('settings.lng');

// Write in memory (non-persistent)
gst.set('items', newItems);

// Write to localStorage (persistent)
gst.local.set('session', userData);

// Write to sessionStorage (session only)
gst.session.set('tempData', data);

// Delete
gst.unset('session');
```

### Automatic Persistence

Keys listed in `config.storage.local` are automatically persisted in localStorage:

```js
// appConfig.js
storage: {
  local: ["session", "settings"],  // Automatically persisted
}
```

## Use Forms

### Complete Form

```jsx
import { useForm, Form, Input, Select, Button } from '@cap-rel/smartcommon';

const CreateItemForm = ({ onSuccess }) => {
  const api = useApi();
  const form = useForm({ defaultValues: { label: '', type: '', description: '' } });

  const handleSubmit = async (data) => {
    try {
      const created = await api.post('items', { json: data });
      onSuccess(created);
    } catch (error) {
      console.error('Create failed:', error);
    }
  };

  return (
    <Form form={form} onSubmit={handleSubmit}>
      <Input name="label" label="Label" />
      <Select
        name="type"
        label="Type"
        options={[
          { value: "A", label: "Type A" },
          { value: "B", label: "Type B" },
          { value: "C", label: "Type C" },
        ]}
      />
      <Input name="description" label="Description" multiline rows={4} />
      <Button type="submit" loading={form.isFormSubmitting}>Create</Button>
    </Form>
  );
};
```

## Build and Deployment

```
# Production build
npm run build

# The build generates the dist/ folder
# Copy it to the pwa/ folder of your module
cp -r dist/* ../pwa/
```

Or use the Makefile:

```
make pwa
```

## See Also
- [Hooks](/front/hooks) - Complete hook documentation
- [SmartCommon](/front/smartcommon) - All available components
- [Configuration](/front/configuration) - Provider options
- [API Requests](/front/requetes-api) - useApi usage
- [PHP Development](/howto/devback) - Backend API
