Skip to content

Latest commit

 

History

History
367 lines (290 loc) · 7.45 KB

File metadata and controls

367 lines (290 loc) · 7.45 KB

Extension Guide

Learn how to extend the boilerplate with new features.

Table of Contents

  1. Adding a New Feature
  2. Adding API Endpoints
  3. Adding New Pages
  4. Adding Components

Adding a New Feature

Step 1: Create Feature Directory

src/features/
├── users/              # New feature
│   ├── components/
│   ├── context/
│   ├── hooks/
│   ├── services/
│   └── index.js

Step 2: Create Service

// src/features/users/services/userService.js
import api from '../../../services/api';

export const userService = {
  getAll: () => api.get('/users'),

  getById: (id) => api.get(`/users/${id}`),

  create: (data) => api.post('/users', data),

  update: (id, data) => api.put(`/users/${id}`, data),

  delete: (id) => api.delete(`/users/${id}`),

  search: (query) => api.get(`/users?q=${query}`),
};

Step 3: Create Context

// src/features/users/context/UserContext.jsx
import { createContext, useContext, useState, useCallback } from 'react';
import { userService } from '../services/userService';

const UserContext = createContext(null);

export function UserProvider({ children }) {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const fetchUsers = useCallback(async () => {
    setLoading(true);
    try {
      const response = await userService.getAll();
      setUsers(response.data);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, []);

  const addUser = useCallback(async (userData) => {
    setLoading(true);
    try {
      const response = await userService.create(userData);
      setUsers(prev => [...prev, response.data]);
      return response.data;
    } catch (err) {
      setError(err.message);
      throw err;
    } finally {
      setLoading(false);
    }
  }, []);

  const removeUser = useCallback(async (id) => {
    setLoading(true);
    try {
      await userService.delete(id);
      setUsers(prev => prev.filter(u => u.id !== id));
    } catch (err) {
      setError(err.message);
      throw err;
    } finally {
      setLoading(false);
    }
  }, []);

  const value = { users, loading, error, fetchUsers, addUser, removeUser };

  return (
    <UserContext.Provider value={value}>
      {children}
    </UserContext.Provider>
  );
}

export const useUsersContext = () => useContext(UserContext);

Step 4: Create Custom Hook

// src/features/users/hooks/useUsers.js
import { useEffect } from 'react';
import { useUsersContext } from '../context/UserContext';

export const useUsers = (autoFetch = true) => {
  const { users, loading, error, fetchUsers, addUser, removeUser } = useUsersContext();

  useEffect(() => {
    if (autoFetch) {
      fetchUsers();
    }
  }, [autoFetch, fetchUsers]);

  return {
    users,
    loading,
    error,
    fetchUsers,
    addUser,
    removeUser,
  };
};

Step 5: Create Components

// src/features/users/components/UserList.jsx
import { useUsers } from '../hooks/useUsers';
import { UserCard } from './UserCard';

export function UserList() {
  const { users, loading, error } = useUsers();

  if (loading) return <Skeleton />;
  if (error) return <ErrorMessage>{error}</ErrorMessage>;

  return (
    <div className="grid grid-cols-3 gap-4">
      {users.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
}

Step 6: Wrap with Provider

// src/main.jsx
import { UserProvider } from './features/users/context/UserContext';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <BrowserRouter>
      <AuthProvider>
        <ProductProvider>
          <UserProvider>  {/* Add new providers here */}
            <App />
          </UserProvider>
        </ProductProvider>
      </AuthProvider>
    </BrowserRouter>
  </React.StrictMode>
);

Adding API Endpoints

For Existing Service

Simply add a new method:

// src/features/products/services/productService.js
export const productService = {
  getAll: () => api.get('/products'),
  getById: (id) => api.get(`/products/${id}`),
  create: (data) => api.post('/products', data),
  update: (id, data) => api.put(`/products/${id}`, data),
  delete: (id) => api.delete(`/products/${id}`),
  
  // New endpoint
  exportCSV: () => api.get('/products/export', { responseType: 'blob' }),
  
  // New endpoint
  bulkCreate: (data) => api.post('/products/bulk', data),
};

For New API Base

Create a separate axios instance:

// src/services/paymentApi.js
import axios from 'axios';

const paymentApi = axios.create({
  baseURL: import.meta.env.VITE_PAYMENT_API_URL,
  timeout: 10000,
});

paymentApi.interceptors.response.use(
  (response) => response,
  (error) => {
    // Handle payment-specific errors
    return Promise.reject(error);
  }
);

export default paymentApi;

Adding New Pages

Step 1: Create Page Component

// src/pages/Users.jsx
import { lazy } from 'react';

function Users() {
  return <div>Users Page</div>;
}

export default Users;

Step 2: Add Route

// src/App.jsx
import { Routes, Route } from 'react-router-dom';
import { lazy, Suspense } from 'react';

const Users = lazy(() => import('./pages/Users'));

function App() {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        <Route index element={<Home />} />
        <Route path="users" element={
          <Suspense fallback={<Skeleton />}>
            <Users />
          </Suspense>
        } />
      </Route>
    </Routes>
  );
}

Step 3: Add Navigation

// src/components/layout/Header.jsx
import { Link } from 'react-router-dom';

export function Header() {
  return (
    <header>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/users">Users</Link>
      </nav>
    </header>
  );
}

Adding Components

UI Component Template

// src/components/ui/ComponentName.jsx
import PropTypes from 'prop-types';

export function ComponentName({ 
  children, 
  variant = 'default',
  size = 'md',
  className = '',
  onClick,
  disabled = false 
}) {
  const baseStyles = 'transition-colors';
  
  const variants = {
    default: 'bg-gray-100',
    primary: 'bg-blue-500 text-white',
    danger: 'bg-red-500 text-white',
  };
  
  const sizes = {
    sm: 'px-2 py-1 text-sm',
    md: 'px-4 py-2',
    lg: 'px-6 py-3 text-lg',
  };

  return (
    <button
      className={`
        ${baseStyles} 
        ${variants[variant]} 
        ${sizes[size]}
        ${disabled ? 'opacity-50 cursor-not-allowed' : ''}
        ${className}
      `}
      onClick={onClick}
      disabled={disabled}
    >
      {children}
    </button>
  );
}

ComponentName.propTypes = {
  children: PropTypes.node.isRequired,
  variant: PropTypes.oneOf(['default', 'primary', 'danger']),
  size: PropTypes.oneOf(['sm', 'md', 'lg']),
  className: PropTypes.string,
  onClick: PropTypes.func,
  disabled: PropTypes.bool,
};

Quick Checklist

When adding a new feature:

  • Create feature directory structure (including context/)
  • Create service file
  • Create Context with state and CRUD methods
  • Create custom hook that uses context
  • Create components
  • Add page route
  • Add provider to main.jsx
  • Add navigation link
  • Add tests