@rashidee/co2 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +25449 -0
- package/drizzle/0000_remarkable_charles_xavier.sql +210 -0
- package/drizzle/0001_wise_wolf_cub.sql +13 -0
- package/drizzle/0002_secret_spyke.sql +16 -0
- package/drizzle/0003_workspace_config_singleton.sql +16 -0
- package/drizzle/0004_terminal_claim_v120.sql +11 -0
- package/drizzle/0005_brainstorm_v124.sql +10 -0
- package/drizzle/0006_project_v140.sql +6 -0
- package/drizzle/0007_prd_wizard_v124.sql +14 -0
- package/drizzle/0008_workflow_artifact_v126.sql +24 -0
- package/drizzle/0009_project_import_v150.sql +7 -0
- package/drizzle/0010_quality_v151.sql +3 -0
- package/drizzle/meta/0000_snapshot.json +1335 -0
- package/drizzle/meta/0001_snapshot.json +1416 -0
- package/drizzle/meta/0002_snapshot.json +1510 -0
- package/drizzle/meta/_journal.json +83 -0
- package/package.json +41 -0
- package/plugin/.claude-plugin/marketplace.json +36 -0
- package/plugin/.claude-plugin/plugin.json +22 -0
- package/plugin/README.md +625 -0
- package/plugin/SKILLS.md +350 -0
- package/plugin/co2.svg +5 -0
- package/plugin/commands/brainstorm-loop.md +129 -0
- package/plugin/skills/conductor-defect/SKILL.md +898 -0
- package/plugin/skills/conductor-feature-develop/SKILL.md +1383 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-domain-template.md +131 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-master-template.md +89 -0
- package/plugin/skills/conductor-feature-develop/references/playwright-setup.md +224 -0
- package/plugin/skills/conductor-feature-prepare/SKILL.md +604 -0
- package/plugin/skills/depgen-k8s/SKILL.md +468 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-laravel.md +197 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-nodejs.md +163 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-spring-boot.md +174 -0
- package/plugin/skills/depgen-k8s/references/k8s-patterns.md +869 -0
- package/plugin/skills/mockgen-shadcn/SKILL.md +1067 -0
- package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +1467 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-index-template.md +216 -0
- package/plugin/skills/mockgen-tailwind/SKILL.md +904 -0
- package/plugin/skills/mockgen-tailwind/references/admin-layout-template.md +720 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-index-template.md +190 -0
- package/plugin/skills/modelgen-nosql/SKILL.md +617 -0
- package/plugin/skills/modelgen-nosql/references/model-extraction-methodology.md +497 -0
- package/plugin/skills/modelgen-nosql/references/nosql-design-guide.md +258 -0
- package/plugin/skills/modelgen-relational/SKILL.md +707 -0
- package/plugin/skills/modelgen-relational/references/model-extraction-methodology.md +499 -0
- package/plugin/skills/specgen-flutter-riverpod/SKILL.md +898 -0
- package/plugin/skills/specgen-flutter-riverpod/references/component-patterns.md +971 -0
- package/plugin/skills/specgen-flutter-riverpod/references/notification-patterns.md +457 -0
- package/plugin/skills/specgen-flutter-riverpod/references/routing-patterns.md +362 -0
- package/plugin/skills/specgen-flutter-riverpod/references/security-patterns.md +709 -0
- package/plugin/skills/specgen-flutter-riverpod/references/spec-template.md +1566 -0
- package/plugin/skills/specgen-flutter-riverpod/references/state-patterns.md +518 -0
- package/plugin/skills/specgen-flutter-riverpod/references/storage-patterns.md +370 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/SKILL.md +984 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/batch-patterns.md +359 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/messaging-patterns.md +468 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/modulith-patterns.md +461 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/reporting-patterns.md +803 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/security-patterns.md +501 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/spec-template.md +1268 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/web-patterns.md +301 -0
- package/plugin/skills/specgen-node-cli-web/SKILL.md +561 -0
- package/plugin/skills/specgen-node-cli-web/references/auth-patterns.md +293 -0
- package/plugin/skills/specgen-node-cli-web/references/cli-packaging-patterns.md +250 -0
- package/plugin/skills/specgen-node-cli-web/references/server-patterns.md +247 -0
- package/plugin/skills/specgen-node-cli-web/references/spec-template.md +401 -0
- package/plugin/skills/specgen-node-cli-web/references/testing-patterns.md +174 -0
- package/plugin/skills/specgen-react-mui/SKILL.md +789 -0
- package/plugin/skills/specgen-react-mui/references/component-patterns.md +1077 -0
- package/plugin/skills/specgen-react-mui/references/reporting-patterns.md +1998 -0
- package/plugin/skills/specgen-react-mui/references/routing-patterns.md +461 -0
- package/plugin/skills/specgen-react-mui/references/security-patterns.md +599 -0
- package/plugin/skills/specgen-react-mui/references/spec-template.md +1030 -0
- package/plugin/skills/specgen-react-mui/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-react-tailwind/SKILL.md +859 -0
- package/plugin/skills/specgen-react-tailwind/references/component-patterns.md +1212 -0
- package/plugin/skills/specgen-react-tailwind/references/reporting-patterns.md +1529 -0
- package/plugin/skills/specgen-react-tailwind/references/routing-patterns.md +669 -0
- package/plugin/skills/specgen-react-tailwind/references/security-patterns.md +656 -0
- package/plugin/skills/specgen-react-tailwind/references/spec-template.md +1237 -0
- package/plugin/skills/specgen-react-tailwind/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-sdk-java/SKILL.md +933 -0
- package/plugin/skills/specgen-sdk-java/references/api-extraction-patterns.md +247 -0
- package/plugin/skills/specgen-sdk-java/references/http-client-patterns.md +495 -0
- package/plugin/skills/specgen-sdk-java/references/packaging-patterns.md +366 -0
- package/plugin/skills/specgen-sdk-java/references/spec-template.md +956 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/SKILL.md +1284 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/jasper-patterns.md +1638 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/modulith-patterns.md +354 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/security-patterns.md +551 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/spec-template.md +2502 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/web-patterns.md +503 -0
- package/plugin/skills/specgen-spring-jpa-restapi/SKILL.md +990 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/modulith-patterns.md +279 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/reporting-patterns.md +1536 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/restapi-patterns.md +722 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/security-patterns.md +351 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/spec-template.md +553 -0
- package/plugin/skills/specgen-ts-cli/SKILL.md +861 -0
- package/plugin/skills/specgen-ts-cli/references/async-patterns.md +1101 -0
- package/plugin/skills/specgen-ts-cli/references/command-patterns.md +383 -0
- package/plugin/skills/specgen-ts-cli/references/config-patterns.md +430 -0
- package/plugin/skills/specgen-ts-cli/references/database-patterns.md +750 -0
- package/plugin/skills/specgen-ts-cli/references/packaging-patterns.md +426 -0
- package/plugin/skills/specgen-ts-cli/references/spec-template.md +1391 -0
- package/plugin/skills/specgen-ts-cli/references/testing-patterns.md +909 -0
- package/plugin/skills/testgen-functional/SKILL.md +1381 -0
- package/plugin/skills/tracegen-matrix/SKILL.md +243 -0
- package/plugin/skills/util-preparek8senv/SKILL.md +422 -0
- package/plugin/skills/util-projectinit/SKILL.md +307 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-loop.md +129 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-protocol.md +144 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-setup-guide.md +153 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-template-appdev.md +87 -0
- package/plugin/skills/util-projectinit/reference/co2-context-generation-guide.md +761 -0
- package/plugin/skills/util-projectsync/SKILL.md +501 -0
- package/plugin/skills/util-updprd/SKILL.md +324 -0
- package/plugin/skills/util-usanalyzer/SKILL.md +307 -0
- package/plugin/skills/util-ustagger/SKILL.md +154 -0
- package/static/assets/abnfDiagram-VRR7QNED-DvPuwyCV.js +1 -0
- package/static/assets/arc-DRKw7sjs.js +1 -0
- package/static/assets/architectureDiagram-ZJ3FMSHR-CaNcxUbC.js +36 -0
- package/static/assets/blockDiagram-677ZJIJ3-Demd4AOK.js +132 -0
- package/static/assets/c4Diagram-LMCZKHZV-PVKUfc2x.js +10 -0
- package/static/assets/channel-C2nVbuwv.js +1 -0
- package/static/assets/chunk-2Q5K7J3B-CExf8ww_.js +1 -0
- package/static/assets/chunk-32BRIVSS-CFPaoONF.js +1 -0
- package/static/assets/chunk-5VM5RSS4-DE2esM67.js +15 -0
- package/static/assets/chunk-EX3LRPZG-SON_YdDG.js +231 -0
- package/static/assets/chunk-JWPE2WC7-Qxah8LNr.js +1 -0
- package/static/assets/chunk-MOJQB5TN-BcWGQV3j.js +88 -0
- package/static/assets/chunk-RYQCIY6F-DkDj-OR5.js +1 -0
- package/static/assets/chunk-V7JOEXUC-BlI1Mrah.js +206 -0
- package/static/assets/chunk-VR4S4FIN-Agf__duj.js +1 -0
- package/static/assets/chunk-XXDRQBXY-B5CfAh85.js +1 -0
- package/static/assets/classDiagram-OUVF2IWQ-DXJIljsR.js +1 -0
- package/static/assets/classDiagram-v2-EOCWNBFH-DXJIljsR.js +1 -0
- package/static/assets/cose-bilkent-JH36ORCC-DO0cQkcy.js +1 -0
- package/static/assets/cynefin-VYW2F7L2-CfOnsnXc.js +178 -0
- package/static/assets/cynefinDiagram-TSTJHNR4-BJ8tULAv.js +62 -0
- package/static/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
- package/static/assets/dagre-VKFMJZFB-DfjwzHqP.js +4 -0
- package/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/static/assets/diagram-FQU43EPY-CndSOIIM.js +3 -0
- package/static/assets/diagram-G47NLZAW-Czf5hN0Z.js +24 -0
- package/static/assets/diagram-NH7WQ7WH-COAyojGh.js +24 -0
- package/static/assets/diagram-OA4YK3LP-H8PoiN2m.js +30 -0
- package/static/assets/diagram-WEI45ONY-DzcaVzjp.js +41 -0
- package/static/assets/ebnfDiagram-CCIWWBDH-DPazGYLJ.js +1 -0
- package/static/assets/erDiagram-Q63AITRT-Bo-fubgF.js +85 -0
- package/static/assets/flowDiagram-23GEKE2U-CSvxUDwh.js +156 -0
- package/static/assets/ganttDiagram-NO4QXBWP-DnBveWX6.js +292 -0
- package/static/assets/gitGraphDiagram-IHSO6WYX-B3rVL8c-.js +106 -0
- package/static/assets/graph-C9eacEi8.js +1 -0
- package/static/assets/index-LZSQkLE2.css +41 -0
- package/static/assets/index-y7XDSgMG.js +495 -0
- package/static/assets/infoDiagram-FWYZ7A6U-CvWV9yy2.js +2 -0
- package/static/assets/init-Gi6I4Gst.js +1 -0
- package/static/assets/ishikawaDiagram-FXEZZL3T-DF28a1N6.js +70 -0
- package/static/assets/journeyDiagram-5HDEW3XC-CB6CugYk.js +139 -0
- package/static/assets/kanban-definition-HUTT4EX6-ResSLF6d.js +89 -0
- package/static/assets/katex-C5jXJg4s.js +257 -0
- package/static/assets/layout-DEXfKzaS.js +1 -0
- package/static/assets/linear-CLO--ryk.js +1 -0
- package/static/assets/map-Czzmt4hB.js +1 -0
- package/static/assets/mindmap-definition-LN4V7U3C-BvW3EbxX.js +96 -0
- package/static/assets/ordinal-Cboi1Yqb.js +1 -0
- package/static/assets/pegDiagram-2B236MQR-BHCgqrrM.js +1 -0
- package/static/assets/pieDiagram-ENE6RG2P-DVmCQ5FA.js +39 -0
- package/static/assets/quadrantDiagram-ABIIQ3AL-B3e5KR52.js +7 -0
- package/static/assets/railroadDiagram-RFXS5EU6-Csv5ysLc.js +1 -0
- package/static/assets/requirementDiagram-TGXJPOKE-DOMVzvnB.js +84 -0
- package/static/assets/sankeyDiagram-HTMAVEWB-B1dcTOvy.js +40 -0
- package/static/assets/sequenceDiagram-DBY2YBRQ-BY414yeO.js +162 -0
- package/static/assets/sizeCapture-X5ZJPWSS-CuJfrfBN.js +1 -0
- package/static/assets/stateDiagram-2N3HPSRC-FX4rJkzV.js +1 -0
- package/static/assets/stateDiagram-v2-6OUMAXLB-BVufbO_H.js +1 -0
- package/static/assets/swimlanes-5IMT3BWC-Sfy58mmv.js +2 -0
- package/static/assets/swimlanesDiagram-G3AALYLV-wDCDw08H.js +8 -0
- package/static/assets/timeline-definition-FHXFAJF6-CmV7by8p.js +120 -0
- package/static/assets/vennDiagram-L72KCM5P-FqIyFZFt.js +34 -0
- package/static/assets/wardleyDiagram-EHGQE667-BZ06O45i.js +78 -0
- package/static/assets/xychartDiagram-FW5EYKEG-BOkffR06.js +7 -0
- package/static/index.html +24 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# State Management Patterns — Zustand Feature Stores & TanStack Query
|
|
2
|
+
|
|
3
|
+
This reference describes when and how to use feature-level Zustand stores alongside
|
|
4
|
+
TanStack Query. Include this content in Section 9 of the generated specification and
|
|
5
|
+
in any module SPEC.md that requires client-side state beyond server cache.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## State Separation Principle
|
|
10
|
+
|
|
11
|
+
The golden rule for React SPA state management:
|
|
12
|
+
|
|
13
|
+
| State Type | Tool | Examples |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| **Server state** (data from API) | TanStack Query | Hero section list, user profile, blog posts |
|
|
16
|
+
| **UI state** (ephemeral, per-session) | Zustand | Selected rows, filter panel open/closed, active tab |
|
|
17
|
+
| **Global UI state** | Zustand (persisted) | Theme mode, sidebar open, user preferences |
|
|
18
|
+
| **Auth state** | Zustand (memory-only, no persist) | JWT token, logged-in user info |
|
|
19
|
+
| **Form state** | React Hook Form | All form field values and validation errors |
|
|
20
|
+
|
|
21
|
+
**Do NOT use Zustand to cache API responses.** If data comes from the server, TanStack
|
|
22
|
+
Query owns it. Zustand holds only the filters/pagination params that TanStack Query uses
|
|
23
|
+
as its query key.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Feature Store Pattern
|
|
28
|
+
|
|
29
|
+
Create a feature store only when the module has complex UI state that needs to be shared
|
|
30
|
+
between sibling components (e.g., a list page and a detail panel rendered side-by-side).
|
|
31
|
+
|
|
32
|
+
### When to create a feature store
|
|
33
|
+
|
|
34
|
+
| Scenario | Use feature store? |
|
|
35
|
+
|---|---|
|
|
36
|
+
| List page with filter bar → filter values | **Yes** — filter state drives TanStack Query params |
|
|
37
|
+
| Multi-step form with shared wizard state | **Yes** — wizard step and accumulated values |
|
|
38
|
+
| List page + slide-over detail panel (side-by-side) | **Yes** — selected item ID |
|
|
39
|
+
| Simple list page with local filter `useState` | **No** — keep state local |
|
|
40
|
+
| Simple create/edit form | **No** — React Hook Form handles form state |
|
|
41
|
+
|
|
42
|
+
### Feature Store Template
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// Example: src/features/heroSection/store/heroSection.store.ts
|
|
46
|
+
import { create } from 'zustand';
|
|
47
|
+
import type { HeroSectionListParams, HeroSectionStatus } from '../heroSection.types';
|
|
48
|
+
|
|
49
|
+
interface HeroSectionState {
|
|
50
|
+
// Filter and pagination params — these drive the TanStack Query key
|
|
51
|
+
filters: HeroSectionListParams;
|
|
52
|
+
|
|
53
|
+
// UI state
|
|
54
|
+
selectedIds: Set<string>;
|
|
55
|
+
isDetailPanelOpen: boolean;
|
|
56
|
+
selectedId: string | null;
|
|
57
|
+
|
|
58
|
+
// Actions
|
|
59
|
+
setFilter: (key: keyof HeroSectionListParams, value: unknown) => void;
|
|
60
|
+
resetFilters: () => void;
|
|
61
|
+
setPage: (page: number) => void;
|
|
62
|
+
toggleSelectId: (id: string) => void;
|
|
63
|
+
clearSelection: () => void;
|
|
64
|
+
openDetail: (id: string) => void;
|
|
65
|
+
closeDetail: () => void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const DEFAULT_FILTERS: HeroSectionListParams = {
|
|
69
|
+
page: 0,
|
|
70
|
+
size: 10,
|
|
71
|
+
status: '',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const useHeroSectionStore = create<HeroSectionState>((set) => ({
|
|
75
|
+
filters: { ...DEFAULT_FILTERS },
|
|
76
|
+
selectedIds: new Set(),
|
|
77
|
+
isDetailPanelOpen: false,
|
|
78
|
+
selectedId: null,
|
|
79
|
+
|
|
80
|
+
setFilter: (key, value) =>
|
|
81
|
+
set((state) => ({
|
|
82
|
+
filters: { ...state.filters, [key]: value, page: 0 },
|
|
83
|
+
})),
|
|
84
|
+
|
|
85
|
+
resetFilters: () => set({ filters: { ...DEFAULT_FILTERS } }),
|
|
86
|
+
|
|
87
|
+
setPage: (page) =>
|
|
88
|
+
set((state) => ({ filters: { ...state.filters, page } })),
|
|
89
|
+
|
|
90
|
+
toggleSelectId: (id) =>
|
|
91
|
+
set((state) => {
|
|
92
|
+
const next = new Set(state.selectedIds);
|
|
93
|
+
next.has(id) ? next.delete(id) : next.add(id);
|
|
94
|
+
return { selectedIds: next };
|
|
95
|
+
}),
|
|
96
|
+
|
|
97
|
+
clearSelection: () => set({ selectedIds: new Set() }),
|
|
98
|
+
|
|
99
|
+
openDetail: (id) => set({ selectedId: id, isDetailPanelOpen: true }),
|
|
100
|
+
|
|
101
|
+
closeDetail: () => set({ isDetailPanelOpen: false, selectedId: null }),
|
|
102
|
+
}));
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Integration with TanStack Query
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
// src/features/heroSection/pages/HeroSectionListPage.tsx
|
|
109
|
+
import { useHeroSectionStore } from '../store/heroSection.store';
|
|
110
|
+
import { useHeroSections } from '../hooks/useHeroSections';
|
|
111
|
+
|
|
112
|
+
export function HeroSectionListPage() {
|
|
113
|
+
// Filter state lives in Zustand — changes trigger React re-render
|
|
114
|
+
const { filters, setFilter, setPage } = useHeroSectionStore();
|
|
115
|
+
|
|
116
|
+
// TanStack Query uses filters as the query key — auto-refetches when filters change
|
|
117
|
+
const { data, isLoading } = useHeroSections(filters);
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<>
|
|
121
|
+
<FilterBar
|
|
122
|
+
status={filters.status}
|
|
123
|
+
onStatusChange={(value) => setFilter('status', value)}
|
|
124
|
+
/>
|
|
125
|
+
<DataTable
|
|
126
|
+
rows={data?.content ?? []}
|
|
127
|
+
loading={isLoading}
|
|
128
|
+
/>
|
|
129
|
+
<TablePagination
|
|
130
|
+
count={data?.totalElements ?? 0}
|
|
131
|
+
page={filters.page ?? 0}
|
|
132
|
+
onPageChange={(_, page) => setPage(page)}
|
|
133
|
+
/>
|
|
134
|
+
</>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Optimistic Updates Pattern
|
|
142
|
+
|
|
143
|
+
For fast UI feedback on mutations (useful for toggle operations like status changes):
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// src/features/heroSection/hooks/useHeroSectionMutations.ts
|
|
147
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
148
|
+
import { heroSectionApi } from '../api/heroSectionApi';
|
|
149
|
+
import { heroSectionKeys } from './useHeroSections';
|
|
150
|
+
import type { HeroSection } from '../heroSection.types';
|
|
151
|
+
|
|
152
|
+
export function useToggleHeroSectionStatus() {
|
|
153
|
+
const queryClient = useQueryClient();
|
|
154
|
+
|
|
155
|
+
return useMutation({
|
|
156
|
+
mutationFn: ({ id, status }: { id: string; status: HeroSection['status'] }) =>
|
|
157
|
+
heroSectionApi.update(id, { status }),
|
|
158
|
+
|
|
159
|
+
// Optimistic update — immediately update the cache before the API responds
|
|
160
|
+
onMutate: async ({ id, status }) => {
|
|
161
|
+
// Cancel any in-flight refetches to avoid overwriting optimistic update
|
|
162
|
+
await queryClient.cancelQueries({ queryKey: heroSectionKeys.all });
|
|
163
|
+
|
|
164
|
+
// Snapshot previous data for rollback
|
|
165
|
+
const previousData = queryClient.getQueryData<HeroSection>(
|
|
166
|
+
heroSectionKeys.detail(id),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Optimistically update the cache
|
|
170
|
+
queryClient.setQueryData<HeroSection>(heroSectionKeys.detail(id), (old) =>
|
|
171
|
+
old ? { ...old, status } : old,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
return { previousData };
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
// On error, roll back to the previous value
|
|
178
|
+
onError: (_error, { id }, context) => {
|
|
179
|
+
if (context?.previousData) {
|
|
180
|
+
queryClient.setQueryData(heroSectionKeys.detail(id), context.previousData);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// Always refetch after error or success to sync with server
|
|
185
|
+
onSettled: (_, __, { id }) => {
|
|
186
|
+
queryClient.invalidateQueries({ queryKey: heroSectionKeys.detail(id) });
|
|
187
|
+
queryClient.invalidateQueries({ queryKey: heroSectionKeys.lists() });
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Multi-Step Form Pattern (Wizard)
|
|
196
|
+
|
|
197
|
+
For complex forms spanning multiple steps (e.g., blog post creation with metadata, content, and preview steps):
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// src/features/blog/store/blogCreate.store.ts
|
|
201
|
+
import { create } from 'zustand';
|
|
202
|
+
import type { BlogCreateStep1Values, BlogCreateStep2Values } from '../blog.schema';
|
|
203
|
+
|
|
204
|
+
type WizardStep = 'metadata' | 'content' | 'preview';
|
|
205
|
+
|
|
206
|
+
interface BlogCreateState {
|
|
207
|
+
currentStep: WizardStep;
|
|
208
|
+
step1Data: BlogCreateStep1Values | null;
|
|
209
|
+
step2Data: BlogCreateStep2Values | null;
|
|
210
|
+
|
|
211
|
+
goToStep: (step: WizardStep) => void;
|
|
212
|
+
saveStep1: (data: BlogCreateStep1Values) => void;
|
|
213
|
+
saveStep2: (data: BlogCreateStep2Values) => void;
|
|
214
|
+
reset: () => void;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export const useBlogCreateStore = create<BlogCreateState>((set) => ({
|
|
218
|
+
currentStep: 'metadata',
|
|
219
|
+
step1Data: null,
|
|
220
|
+
step2Data: null,
|
|
221
|
+
|
|
222
|
+
goToStep: (step) => set({ currentStep: step }),
|
|
223
|
+
saveStep1: (data) => set({ step1Data: data, currentStep: 'content' }),
|
|
224
|
+
saveStep2: (data) => set({ step2Data: data, currentStep: 'preview' }),
|
|
225
|
+
reset: () => set({ currentStep: 'metadata', step1Data: null, step2Data: null }),
|
|
226
|
+
}));
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Cache Invalidation Strategy
|
|
232
|
+
|
|
233
|
+
Follow this decision tree when invalidating TanStack Query cache after mutations:
|
|
234
|
+
|
|
235
|
+
| Mutation | Invalidation Target |
|
|
236
|
+
|---|---|
|
|
237
|
+
| Create item | `queryKey: entityKeys.lists()` — all list queries |
|
|
238
|
+
| Update item | `queryKey: entityKeys.detail(id)` + `entityKeys.lists()` |
|
|
239
|
+
| Delete item | `queryKey: entityKeys.lists()` |
|
|
240
|
+
| Bulk action (delete multiple) | `queryKey: entityKeys.all` |
|
|
241
|
+
| Status toggle | `queryKey: entityKeys.detail(id)` + `entityKeys.lists()` |
|
|
242
|
+
|
|
243
|
+
Always use `invalidateQueries` (not `removeQueries`) so the UI shows stale data with
|
|
244
|
+
a loading indicator rather than an empty flash.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Prefetching for Navigation
|
|
249
|
+
|
|
250
|
+
Prefetch list data when hovering a navigation link to make page transitions feel instant:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// In AppSidebar or NavigationLink component
|
|
254
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
255
|
+
import { heroSectionKeys } from '../../features/heroSection/hooks/useHeroSections';
|
|
256
|
+
import { heroSectionApi } from '../../features/heroSection/api/heroSectionApi';
|
|
257
|
+
|
|
258
|
+
function NavItemWithPrefetch({ item }: { item: NavItem }) {
|
|
259
|
+
const queryClient = useQueryClient();
|
|
260
|
+
|
|
261
|
+
const handleMouseEnter = () => {
|
|
262
|
+
// Prefetch the default page when the user hovers the nav item
|
|
263
|
+
queryClient.prefetchQuery({
|
|
264
|
+
queryKey: heroSectionKeys.list({ page: 0, size: 10 }),
|
|
265
|
+
queryFn: () => heroSectionApi.list({ page: 0, size: 10 }),
|
|
266
|
+
staleTime: 30_000,
|
|
267
|
+
});
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<ListItemButton onMouseEnter={handleMouseEnter} onClick={() => navigate(item.path)}>
|
|
272
|
+
...
|
|
273
|
+
</ListItemButton>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Error State Handling in Pages
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
// Pattern for handling query errors in page components
|
|
284
|
+
import { Alert, Button, Box } from '@mui/material';
|
|
285
|
+
|
|
286
|
+
export function HeroSectionListPage() {
|
|
287
|
+
const { data, isLoading, isError, error, refetch } = useHeroSections(filters);
|
|
288
|
+
|
|
289
|
+
if (isError) {
|
|
290
|
+
return (
|
|
291
|
+
<Box sx={{ mt: 4 }}>
|
|
292
|
+
<Alert
|
|
293
|
+
severity="error"
|
|
294
|
+
action={
|
|
295
|
+
<Button color="inherit" size="small" onClick={() => refetch()}>
|
|
296
|
+
Retry
|
|
297
|
+
</Button>
|
|
298
|
+
}
|
|
299
|
+
>
|
|
300
|
+
{error instanceof Error ? error.message : 'Failed to load data. Please try again.'}
|
|
301
|
+
</Alert>
|
|
302
|
+
</Box>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Normal render...
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Scroll Position Restoration
|
|
313
|
+
|
|
314
|
+
React Router v7 handles scroll restoration automatically when navigating between routes.
|
|
315
|
+
For pages with virtual scroll or infinite pagination, use TanStack Query's `keepPreviousData`
|
|
316
|
+
equivalent:
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
export function useHeroSections(params: HeroSectionListParams) {
|
|
320
|
+
return useQuery({
|
|
321
|
+
queryKey: heroSectionKeys.list(params),
|
|
322
|
+
queryFn: () => heroSectionApi.list(params),
|
|
323
|
+
// Keep showing previous data while new data loads (avoids empty flash on page change)
|
|
324
|
+
placeholderData: (previousData) => previousData,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
```
|