Frontend Module Creation
How to create a new feature module in fe/.
Architecture
| Directory | Purpose |
|---|---|
fe/src/core-design/ | Reusable primitives (buttons, inputs, modals) |
fe/src/page/ | Thin route wrappers |
fe/src/components/<module>/ | Feature modules |
fe/src/services/<module>/ | API layer (fetch + React Query) |
fe/src/store/ | Global Redux store |
State Management (3-Tier)
mermaid
flowchart TD
Q1{Server data?}
Q1 -->|Yes| TQ[TanStack Query]
Q1 -->|No| Q2{Multiple features?}
Q2 -->|Yes| RX[Redux global]
Q2 -->|No| Q3{One feature?}
Q3 -->|Yes| ZS[Zustand module store]
Q3 -->|No| US[React useState]
1. TanStack Query (Server State)
- •Where:
services/<module>/useQuery.ts - •Use for: API data, cache, invalidation
2. Zustand (Module UI State)
- •Where:
components/<module>/store.ts - •Use for: View mode, menu state, form state, selections
- •Rule: Store ids + flags, not heavy objects
3. Redux (Global UI State)
- •Where:
store/*.store.ts - •Use for: Modal stack, app settings, cross-feature state
Steps to Create Module
1. Create Module Directory
Simple module:
code
fe/src/components/<module-name>/ ├── store.ts # Zustand (if needed) ├── MainComponent.tsx # Main component ├── CreateDialog.tsx # Create modal └── utils.ts # Helpers
Complex module with multi-layer form:
code
fe/src/components/<module-name>/ ├── store.ts # Zustand UI state ├── MainComponent.tsx # Main component ├── <FeatureName>/ # Sub-feature folder │ ├── index.tsx # Entry (owns FormProvider) │ ├── store.ts # Sub-feature Zustand store │ ├── schema.ts # yup/zod schema + defaults │ ├── types.ts # Form types │ ├── utils.ts # Helpers │ └── sections/ # Form sections (use useFormContext) │ ├── BasicSection.tsx │ └── AdvancedSection.tsx └── utils.ts # Shared helpers
2. Create Service Layer
code
fe/src/services/<module-name>/ ├── api.ts # Low-level fetch calls └── useQuery.ts # TanStack Query hooks
api.ts:
typescript
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
export const createItem = async (request: CreateRequest, storeId?: string) => {
const response = await fetch(`${API_BASE_URL}/v1/item/CreateItem`, {
method: "POST",
headers: { "Content-Type": "application/json", ...(storeId && { "x-store-id": storeId }) },
credentials: "include",
body: JSON.stringify(request),
});
const data = await response.json();
if (!response.ok && data.success === false) throw new Error(data.error.message);
return data;
};
useQuery.ts:
typescript
export const useCreateItem = ({ storeId, onSuccess, onError }) => {
return useMutation({
mutationFn: (request) => createItem(request, storeId),
onSuccess,
onError,
});
};
3. Create Module Store (Zustand)
typescript
// components/<module>/store.ts
import { create } from "zustand";
interface ModuleStore {
viewMode: "grid" | "list";
activeMenuId: string | null;
setViewMode: (mode: "grid" | "list") => void;
setActiveMenuId: (id: string | null) => void;
}
export const useModuleStore = create<ModuleStore>((set) => ({
viewMode: "grid",
activeMenuId: null,
setViewMode: (mode) => set({ viewMode: mode }),
setActiveMenuId: (id) => set({ activeMenuId: id }),
}));
4. Multi-Layer Form Pattern (FormProvider)
For complex create/edit flows with multiple sections:
Parent (index.tsx) owns useForm + wraps children with FormProvider:
typescript
import { FormProvider, useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import { schema, defaultValues } from "./schema";
export default function CreateFeature() {
const form = useForm({ defaultValues, resolver: yupResolver(schema) });
return (
<FormProvider {...form}>
<BasicSection />
<AdvancedSection />
</FormProvider>
);
}
Child sections use useFormContext (no props drilling):
typescript
import { useFormContext, useWatch } from "react-hook-form";
export function BasicSection() {
const { register, setValue, control } = useFormContext<FormValues>();
const watchedField = useWatch({ control, name: "fieldName" });
// ...
}
schema.ts defines validation + defaults:
typescript
import * as yup from "yup";
export const schema = yup.object().shape({
name: yup.string().required("Name is required"),
price: yup.string().required("Price is required"),
});
export const defaultValues = { name: "", price: "" };
5. Create Entry Page
typescript
// page/Module.tsx import MainModule from "@Jade/components/module-name/MainModule"; const Module = () => <MainModule />; export default Module;
6. Add Route
typescript
<Route path="/module" element={<Module />} />
Checklist
- • Created
components/<module>/ - • Created
services/<module>/(api.ts + useQuery.ts) - • Created module store if needed
- • Created entry page in
page/ - • Added route
- • Used TanStack Query for server state
- • Used Zustand for module UI state
- • Used FormProvider + useFormContext for multi-layer forms
- • Created schema.ts + types.ts for form validation
- • Followed dark mode patterns
- • Used
core-designcomponents