@famgia/omnify-ai-guides 2.0.15

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.
Files changed (91) hide show
  1. package/README.md +105 -0
  2. package/dist/chunk-RCTEXK7C.js +549 -0
  3. package/dist/chunk-RCTEXK7C.js.map +1 -0
  4. package/dist/config/rules.yaml +524 -0
  5. package/dist/index.cjs +587 -0
  6. package/dist/index.cjs.map +1 -0
  7. package/dist/index.d.cts +55 -0
  8. package/dist/index.d.ts +55 -0
  9. package/dist/index.js +26 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/knowledge/agents/architect.md.stub +150 -0
  12. package/dist/knowledge/agents/developer.md.stub +190 -0
  13. package/dist/knowledge/agents/reviewer.md.stub +134 -0
  14. package/dist/knowledge/agents/tester.md.stub +196 -0
  15. package/dist/knowledge/checklists/backend.md.stub +112 -0
  16. package/dist/knowledge/checklists/react.md.stub +108 -0
  17. package/dist/knowledge/claude-rules/laravel-controllers.md.stub +57 -0
  18. package/dist/knowledge/claude-rules/laravel-migrations.md.stub +47 -0
  19. package/dist/knowledge/claude-rules/laravel-tests.md.stub +52 -0
  20. package/dist/knowledge/claude-rules/naming.md.stub +369 -0
  21. package/dist/knowledge/claude-rules/performance.md.stub +256 -0
  22. package/dist/knowledge/claude-rules/php-standards.md.stub +305 -0
  23. package/dist/knowledge/claude-rules/react-components.md.stub +67 -0
  24. package/dist/knowledge/claude-rules/schema-yaml.md.stub +83 -0
  25. package/dist/knowledge/claude-rules/security.md.stub +164 -0
  26. package/dist/knowledge/cursor-rules/antd-deprecations.mdc.stub +62 -0
  27. package/dist/knowledge/cursor-rules/basemodel-readonly.mdc.stub +66 -0
  28. package/dist/knowledge/cursor-rules/baserequest-readonly.mdc.stub +74 -0
  29. package/dist/knowledge/cursor-rules/baseresource-readonly.mdc.stub +78 -0
  30. package/dist/knowledge/cursor-rules/laravel-controller.mdc.stub +421 -0
  31. package/dist/knowledge/cursor-rules/laravel-request.mdc.stub +112 -0
  32. package/dist/knowledge/cursor-rules/laravel-resource.mdc.stub +73 -0
  33. package/dist/knowledge/cursor-rules/laravel-review.mdc.stub +69 -0
  34. package/dist/knowledge/cursor-rules/laravel-testing.mdc.stub +138 -0
  35. package/dist/knowledge/cursor-rules/laravel.mdc.stub +138 -0
  36. package/dist/knowledge/cursor-rules/migrations-workflow.mdc.stub +224 -0
  37. package/dist/knowledge/cursor-rules/model-editable.mdc.stub +120 -0
  38. package/dist/knowledge/cursor-rules/omnify-migrations.mdc.stub +109 -0
  39. package/dist/knowledge/cursor-rules/omnify-schema.mdc.stub +358 -0
  40. package/dist/knowledge/cursor-rules/omnify.mdc.stub +58 -0
  41. package/dist/knowledge/cursor-rules/react-design.mdc.stub +693 -0
  42. package/dist/knowledge/cursor-rules/react-form.mdc.stub +292 -0
  43. package/dist/knowledge/cursor-rules/react-services.mdc.stub +304 -0
  44. package/dist/knowledge/cursor-rules/react.mdc.stub +336 -0
  45. package/dist/knowledge/cursor-rules/request-editable.mdc.stub +111 -0
  46. package/dist/knowledge/cursor-rules/resource-editable.mdc.stub +125 -0
  47. package/dist/knowledge/cursor-rules/schema-create.mdc.stub +440 -0
  48. package/dist/knowledge/cursor-rules/validation-rules.mdc.stub +181 -0
  49. package/dist/knowledge/laravel/README.md.stub +59 -0
  50. package/dist/knowledge/laravel/architecture.md.stub +424 -0
  51. package/dist/knowledge/laravel/authentication.md.stub +588 -0
  52. package/dist/knowledge/laravel/controller.md.stub +484 -0
  53. package/dist/knowledge/laravel/datetime.md.stub +334 -0
  54. package/dist/knowledge/laravel/migrations-team.md.stub +376 -0
  55. package/dist/knowledge/laravel/openapi.md.stub +449 -0
  56. package/dist/knowledge/laravel/request.md.stub +450 -0
  57. package/dist/knowledge/laravel/resource.md.stub +516 -0
  58. package/dist/knowledge/laravel/service.md.stub +503 -0
  59. package/dist/knowledge/laravel/testing.md.stub +1504 -0
  60. package/dist/knowledge/omnify/antdesign-guide.md.stub +401 -0
  61. package/dist/knowledge/omnify/config-guide.md.stub +405 -0
  62. package/dist/knowledge/omnify/japan-guide.md.stub +186 -0
  63. package/dist/knowledge/omnify/laravel-guide.md.stub +61 -0
  64. package/dist/knowledge/omnify/partial-schema-guide.md.stub +353 -0
  65. package/dist/knowledge/omnify/react-form-guide.md.stub +225 -0
  66. package/dist/knowledge/omnify/schema-guide.md.stub +144 -0
  67. package/dist/knowledge/omnify/typescript-guide.md.stub +337 -0
  68. package/dist/knowledge/react/README.md.stub +221 -0
  69. package/dist/knowledge/react/antd-guide.md +528 -0
  70. package/dist/knowledge/react/antd-guide.md.stub +528 -0
  71. package/dist/knowledge/react/checklist.md.stub +108 -0
  72. package/dist/knowledge/react/datetime-guide.md.stub +137 -0
  73. package/dist/knowledge/react/design-philosophy.md.stub +363 -0
  74. package/dist/knowledge/react/i18n-guide.md.stub +211 -0
  75. package/dist/knowledge/react/laravel-integration.md.stub +181 -0
  76. package/dist/knowledge/react/service-pattern.md.stub +180 -0
  77. package/dist/knowledge/react/tanstack-query.md.stub +339 -0
  78. package/dist/knowledge/react/types-guide.md +669 -0
  79. package/dist/knowledge/react/types-guide.md.stub +669 -0
  80. package/dist/knowledge/workflows/bug-fix.md.stub +201 -0
  81. package/dist/knowledge/workflows/code-review.md.stub +164 -0
  82. package/dist/knowledge/workflows/new-feature.md.stub +327 -0
  83. package/dist/plugin-M95GyBll.d.cts +191 -0
  84. package/dist/plugin-M95GyBll.d.ts +191 -0
  85. package/dist/plugin.cjs +573 -0
  86. package/dist/plugin.cjs.map +1 -0
  87. package/dist/plugin.d.cts +2 -0
  88. package/dist/plugin.d.ts +2 -0
  89. package/dist/plugin.js +15 -0
  90. package/dist/plugin.js.map +1 -0
  91. package/package.json +53 -0
@@ -0,0 +1,181 @@
1
+ # Laravel Integration
2
+
3
+ > **Related:** [README](./README.md) | [Service Pattern](./service-pattern.md)
4
+
5
+ ## Sanctum Authentication Flow
6
+
7
+ ```typescript
8
+ // Step 1: Get CSRF cookie (required before POST requests)
9
+ await api.get("/sanctum/csrf-cookie");
10
+
11
+ // Step 2: Login
12
+ await api.post("/login", { email, password });
13
+ // Cookie is now set automatically
14
+
15
+ // Step 3: Access protected routes
16
+ await api.get("/api/user"); // Works! Cookie sent automatically
17
+ ```
18
+
19
+ ---
20
+
21
+ ## Error Handling Map
22
+
23
+ | HTTP Status | Laravel Meaning | Frontend Action |
24
+ | ----------- | ------------------- | ------------------------- |
25
+ | 200 | Success | Process response |
26
+ | 201 | Created | Process response |
27
+ | 204 | No Content | Success (no body) |
28
+ | 401 | Unauthenticated | Redirect to `/login` |
29
+ | 403 | Forbidden | Show error message |
30
+ | 404 | Not Found | Show error message |
31
+ | 419 | CSRF Token Mismatch | Refresh page |
32
+ | 422 | Validation Error | Display in form fields |
33
+ | 429 | Too Many Requests | Show rate limit message |
34
+ | 500+ | Server Error | Show server error message |
35
+
36
+ ---
37
+
38
+ ## API Response Types
39
+
40
+ ### Laravel Pagination Response
41
+
42
+ ```typescript
43
+ interface PaginatedResponse<T> {
44
+ data: T[];
45
+ links: {
46
+ first: string | null;
47
+ last: string | null;
48
+ prev: string | null;
49
+ next: string | null;
50
+ };
51
+ meta: {
52
+ current_page: number;
53
+ from: number | null;
54
+ last_page: number;
55
+ per_page: number;
56
+ to: number | null;
57
+ total: number;
58
+ };
59
+ }
60
+ ```
61
+
62
+ ### Laravel API Resource Response (single item)
63
+
64
+ ```typescript
65
+ interface ResourceResponse<T> {
66
+ data: T;
67
+ }
68
+ ```
69
+
70
+ ### Laravel Validation Error Response (422)
71
+
72
+ ```typescript
73
+ interface ValidationErrorResponse {
74
+ message: string;
75
+ errors: Record<string, string[]>;
76
+ }
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Handling Laravel Responses
82
+
83
+ ### In Service Layer
84
+
85
+ ```typescript
86
+ const userService = {
87
+ // Paginated list
88
+ list: async (params?: UserListParams): Promise<PaginatedResponse<User>> => {
89
+ const { data } = await api.get("/api/users", { params });
90
+ return data; // Already typed as PaginatedResponse
91
+ },
92
+
93
+ // Single resource (handle { data: ... } wrapper)
94
+ get: async (id: number): Promise<User> => {
95
+ const { data } = await api.get(`/api/users/${id}`);
96
+ return data.data ?? data; // Handle both wrapped and unwrapped
97
+ },
98
+ };
99
+ ```
100
+
101
+ ### In Components (Form Validation)
102
+
103
+ ```typescript
104
+ import { getFormErrors } from "@/lib/api";
105
+
106
+ const mutation = useMutation({
107
+ mutationFn: userService.create,
108
+ onError: (error) => {
109
+ // Transform Laravel 422 errors to Ant Design format
110
+ form.setFields(getFormErrors(error));
111
+ },
112
+ });
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Axios Instance Configuration
118
+
119
+ The `lib/api.ts` is pre-configured for Laravel Sanctum:
120
+
121
+ ```typescript
122
+ const api = axios.create({
123
+ baseURL: process.env.NEXT_PUBLIC_API_URL,
124
+ timeout: 30000,
125
+ headers: {
126
+ "Content-Type": "application/json",
127
+ Accept: "application/json",
128
+ },
129
+ withCredentials: true, // Required for Sanctum cookies
130
+ withXSRFToken: true, // Auto send XSRF-TOKEN cookie as header
131
+ xsrfCookieName: "XSRF-TOKEN",
132
+ xsrfHeaderName: "X-XSRF-TOKEN",
133
+ });
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Common Patterns
139
+
140
+ ### Login Flow
141
+
142
+ ```typescript
143
+ import { csrf } from "@/lib/api";
144
+
145
+ const login = async (email: string, password: string) => {
146
+ // 1. Get CSRF cookie first
147
+ await csrf();
148
+
149
+ // 2. Login
150
+ await api.post("/login", { email, password });
151
+
152
+ // 3. Get user data
153
+ const { data } = await api.get("/api/user");
154
+ return data;
155
+ };
156
+ ```
157
+
158
+ ### Protected API Call
159
+
160
+ ```typescript
161
+ // No special handling needed - cookies are sent automatically
162
+ const users = await userService.list();
163
+ ```
164
+
165
+ ### Handling 401 (Unauthenticated)
166
+
167
+ The interceptor in `lib/api.ts` automatically redirects to `/login` on 401:
168
+
169
+ ```typescript
170
+ api.interceptors.response.use(
171
+ (response) => response,
172
+ (error) => {
173
+ if (error.response?.status === 401) {
174
+ if (!window.location.pathname.includes("/login")) {
175
+ window.location.href = "/login";
176
+ }
177
+ }
178
+ return Promise.reject(error);
179
+ }
180
+ );
181
+ ```
@@ -0,0 +1,180 @@
1
+ # Service Layer Pattern
2
+
3
+ > **Related:** [README](./README.md) | [TanStack Query](./tanstack-query.md) | [Laravel Integration](./laravel-integration.md)
4
+
5
+ ## Types Rule
6
+
7
+ ```typescript
8
+ // ✅ DO: Import Model + Create/Update types from Omnify
9
+ import type { User, UserCreate, UserUpdate } from "@omnify/schemas";
10
+
11
+ // ✅ DO: Only define query params locally (not generated by Omnify)
12
+ export interface UserListParams {
13
+ search?: string;
14
+ page?: number;
15
+ }
16
+
17
+ // ❌ DON'T: Define types that Omnify already generates
18
+ export interface UserCreateInput { ... } // WRONG - use UserCreate from Omnify
19
+ export interface User { ... } // WRONG - use User from Omnify
20
+ ```
21
+
22
+ ---
23
+
24
+ ## Service Template
25
+
26
+ ```typescript
27
+ /**
28
+ * Users Service
29
+ *
30
+ * CRUD operations for User resource.
31
+ * Maps to Laravel UserController.
32
+ */
33
+
34
+ import api, { PaginatedResponse } from "@/lib/api";
35
+ import type { User, UserCreate, UserUpdate } from "@omnify/schemas";
36
+
37
+ // =============================================================================
38
+ // Types - Only query params (Create/Update come from Omnify)
39
+ // =============================================================================
40
+
41
+ /** Query params for listing users */
42
+ export interface UserListParams {
43
+ search?: string;
44
+ page?: number;
45
+ per_page?: number;
46
+ sort_by?: keyof User;
47
+ sort_order?: "asc" | "desc";
48
+ }
49
+
50
+ // =============================================================================
51
+ // Service
52
+ // =============================================================================
53
+
54
+ const BASE_URL = "/api/users";
55
+
56
+ export const userService = {
57
+ /**
58
+ * Get paginated list of users
59
+ * GET /api/users
60
+ */
61
+ list: async (params?: UserListParams): Promise<PaginatedResponse<User>> => {
62
+ const { data } = await api.get(BASE_URL, { params });
63
+ return data;
64
+ },
65
+
66
+ /**
67
+ * Get single user by ID
68
+ * GET /api/users/:id
69
+ */
70
+ get: async (id: number): Promise<User> => {
71
+ const { data } = await api.get(`${BASE_URL}/${id}`);
72
+ return data.data ?? data;
73
+ },
74
+
75
+ /**
76
+ * Create new user
77
+ * POST /api/users
78
+ */
79
+ create: async (input: UserCreate): Promise<User> => {
80
+ const { data } = await api.post(BASE_URL, input);
81
+ return data.data ?? data;
82
+ },
83
+
84
+ /**
85
+ * Update existing user
86
+ * PUT /api/users/:id
87
+ */
88
+ update: async (id: number, input: UserUpdate): Promise<User> => {
89
+ const { data } = await api.put(`${BASE_URL}/${id}`, input);
90
+ return data.data ?? data;
91
+ },
92
+
93
+ /**
94
+ * Delete user
95
+ * DELETE /api/users/:id
96
+ */
97
+ delete: async (id: number): Promise<void> => {
98
+ await api.delete(`${BASE_URL}/${id}`);
99
+ },
100
+ };
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Service Rules
106
+
107
+ ```typescript
108
+ // ✅ DO: Import all types from Omnify
109
+ import type { User, UserCreate, UserUpdate } from "@omnify/schemas";
110
+
111
+ // ❌ DON'T: Define types that Omnify generates
112
+ export interface UserCreate { ... } // WRONG!
113
+ export interface User { ... } // WRONG!
114
+
115
+ // ✅ DO: Only define query params locally
116
+ export interface UserListParams { ... } // OK - not in Omnify
117
+
118
+ // ✅ DO: Keep services pure (no React hooks)
119
+ export const userService = {
120
+ get: (id) => api.get(`/api/users/${id}`).then(r => r.data.data ?? r.data),
121
+ };
122
+
123
+ // ❌ DON'T: Use React hooks in services
124
+ export const userService = {
125
+ get: (id) => {
126
+ const [data, setData] = useState(); // WRONG!
127
+ },
128
+ };
129
+
130
+ // ✅ DO: Handle Laravel's { data: ... } wrapper
131
+ get: (id) => api.get(`/api/users/${id}`).then(r => r.data.data ?? r.data),
132
+
133
+ // ❌ DON'T: Return raw axios response
134
+ get: (id) => api.get(`/api/users/${id}`), // Returns AxiosResponse, not data
135
+ ```
136
+
137
+ ---
138
+
139
+ ## Using Validation Rules
140
+
141
+ ```typescript
142
+ import { Form, Input } from "antd";
143
+ import { useLocale } from "next-intl";
144
+ import { userSchemas, getUserFieldLabel } from "@omnify/schemas/User";
145
+ import { zodRule } from "@/lib/form-validation";
146
+
147
+ function UserForm() {
148
+ const locale = useLocale();
149
+ const label = (key: string) => getUserFieldLabel(key, locale);
150
+
151
+ return (
152
+ <Form>
153
+ {/* Name */}
154
+ <Form.Item
155
+ name="name"
156
+ label={label("name")}
157
+ rules={[zodRule(userSchemas.name, label("name"))]}
158
+ >
159
+ <Input />
160
+ </Form.Item>
161
+ </Form>
162
+ );
163
+ }
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Types Summary
169
+
170
+ | Type | Source | Example |
171
+ | -------- | ---------------------------- | ------------------------------------------ |
172
+ | Model | `@omnify/schemas` (Omnify) | `User` |
173
+ | Create | `@omnify/schemas` (Omnify) | `UserCreate` |
174
+ | Update | `@omnify/schemas` (Omnify) | `UserUpdate` |
175
+ | Schemas | `@omnify/schemas` (Omnify) | `userSchemas.email` |
176
+ | Params | Service file (manual) | `UserListParams` |
177
+ | Response | `@/lib/api.ts` | `PaginatedResponse<T>` |
178
+ | Rules | `@/lib/form-validation` | `zodRule(schema, label)` |
179
+
180
+ See [Types Guide](./types-guide.md) for complete details.
@@ -0,0 +1,339 @@
1
+ # TanStack Query Guide
2
+
3
+ > **Related:** [README](./README.md) | [Service Pattern](./service-pattern.md)
4
+
5
+ ## Query Keys Pattern
6
+
7
+ ### Structure
8
+
9
+ ```typescript
10
+ // lib/queryKeys.ts
11
+
12
+ import type { UserListParams } from "@/services/users"; // Import from service
13
+ import type { PostListParams } from "@/services/posts";
14
+
15
+ export const queryKeys = {
16
+ // Simple key
17
+ user: ["user"] as const,
18
+
19
+ // Resource with nested keys - USE TYPED PARAMS
20
+ users: {
21
+ all: ["users"] as const,
22
+ lists: () => [...queryKeys.users.all, "list"] as const,
23
+ list: (params?: UserListParams) => [...queryKeys.users.lists(), params] as const,
24
+ details: () => [...queryKeys.users.all, "detail"] as const,
25
+ detail: (id: number) => [...queryKeys.users.details(), id] as const,
26
+ },
27
+
28
+ posts: {
29
+ all: ["posts"] as const,
30
+ lists: () => [...queryKeys.posts.all, "list"] as const,
31
+ list: (params?: PostListParams) => [...queryKeys.posts.lists(), params] as const,
32
+ details: () => [...queryKeys.posts.all, "detail"] as const,
33
+ detail: (id: number) => [...queryKeys.posts.details(), id] as const,
34
+ byUser: (userId: number) => [...queryKeys.posts.all, "user", userId] as const,
35
+ },
36
+ } as const;
37
+ ```
38
+
39
+ ### ⚠️ Type Rule
40
+
41
+ ```typescript
42
+ // ✅ DO: Import and use specific types from service
43
+ import type { UserListParams } from "@/services/users";
44
+ list: (params?: UserListParams) => [...]
45
+
46
+ // ❌ DON'T: Use generic Record type
47
+ list: (params?: Record<string, unknown>) => [...] // Hard to read, no autocomplete
48
+ ```
49
+
50
+ ### Rules
51
+
52
+ ```typescript
53
+ // ✅ DO: Use query key factory
54
+ useQuery({
55
+ queryKey: queryKeys.users.detail(id),
56
+ queryFn: () => userService.get(id),
57
+ });
58
+
59
+ // ❌ DON'T: Hardcode query keys
60
+ useQuery({
61
+ queryKey: ["users", "detail", id], // Hard to maintain
62
+ queryFn: () => userService.get(id),
63
+ });
64
+
65
+ // ✅ DO: Include all dependencies in key
66
+ useQuery({
67
+ queryKey: queryKeys.users.list({ page, search }), // Refetches when params change
68
+ queryFn: () => userService.list({ page, search }),
69
+ });
70
+
71
+ // ❌ DON'T: Omit dependencies
72
+ useQuery({
73
+ queryKey: queryKeys.users.list(), // Won't refetch when params change
74
+ queryFn: () => userService.list({ page, search }),
75
+ });
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Mutation Pattern
81
+
82
+ ### Standard CRUD Mutations
83
+
84
+ ```typescript
85
+ "use client";
86
+
87
+ import { Form, Button } from "antd";
88
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
89
+ import { message } from "antd";
90
+ import { useRouter } from "next/navigation";
91
+ import { useTranslations } from "next-intl";
92
+ import { queryKeys } from "@/lib/queryKeys";
93
+ import { getFormErrors } from "@/lib/api";
94
+ import { userService, UserUpdateInput } from "@/services/users";
95
+
96
+ export default function CreateUserPage() {
97
+ const t = useTranslations(); // No namespace = access all
98
+ const router = useRouter();
99
+ const queryClient = useQueryClient();
100
+ const [form] = Form.useForm();
101
+
102
+ // CREATE mutation
103
+ const createMutation = useMutation({
104
+ mutationFn: userService.create,
105
+ onSuccess: () => {
106
+ queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
107
+ message.success(t("messages.created"));
108
+ router.push("/users");
109
+ },
110
+ onError: (error) => {
111
+ form.setFields(getFormErrors(error));
112
+ },
113
+ });
114
+
115
+ // UPDATE mutation
116
+ const updateMutation = useMutation({
117
+ mutationFn: ({ id, data }: { id: number; data: UserUpdateInput }) =>
118
+ userService.update(id, data),
119
+ onSuccess: (_, { id }) => {
120
+ queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
121
+ queryClient.invalidateQueries({ queryKey: queryKeys.users.detail(id) });
122
+ message.success(t("messages.updated"));
123
+ router.push(`/users/${id}`);
124
+ },
125
+ onError: (error) => {
126
+ form.setFields(getFormErrors(error));
127
+ },
128
+ });
129
+
130
+ // DELETE mutation
131
+ const deleteMutation = useMutation({
132
+ mutationFn: userService.delete,
133
+ onSuccess: () => {
134
+ queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
135
+ message.success(t("messages.deleted"));
136
+ router.push("/users");
137
+ },
138
+ });
139
+
140
+ return (
141
+ <Form form={form} onFinish={(values) => createMutation.mutate(values)}>
142
+ {/* ... form fields ... */}
143
+ <Button type="primary" htmlType="submit" loading={createMutation.isPending}>
144
+ {t("common.save")}
145
+ </Button>
146
+ </Form>
147
+ );
148
+ }
149
+ ```
150
+
151
+ ### Mutation Rules
152
+
153
+ ```typescript
154
+ // ✅ DO: Always invalidate related queries after mutation
155
+ onSuccess: () => {
156
+ queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
157
+ }
158
+
159
+ // ❌ DON'T: Forget to invalidate
160
+ onSuccess: () => {
161
+ message.success("Saved!"); // Data won't refresh!
162
+ }
163
+
164
+ // ✅ DO: Handle form errors from Laravel
165
+ onError: (error) => {
166
+ form.setFields(getFormErrors(error));
167
+ }
168
+
169
+ // ❌ DON'T: Ignore errors
170
+ onError: (error) => {
171
+ console.log(error); // User sees nothing
172
+ }
173
+
174
+ // ✅ DO: Show loading state
175
+ <Button loading={mutation.isPending}>Submit</Button>
176
+
177
+ // ❌ DON'T: No loading feedback
178
+ <Button>Submit</Button> // User can double-click
179
+ ```
180
+
181
+ ---
182
+
183
+ ## Advanced Tips
184
+
185
+ ### Keep Queries Simple
186
+
187
+ ```typescript
188
+ // ✅ SIMPLE: queryFn just calls service
189
+ useQuery({
190
+ queryKey: queryKeys.users.list(filters),
191
+ queryFn: () => userService.list(filters),
192
+ });
193
+
194
+ // ❌ OVER-ENGINEERED: Logic in queryFn
195
+ useQuery({
196
+ queryKey: queryKeys.users.list(filters),
197
+ queryFn: async () => {
198
+ const data = await userService.list(filters);
199
+ return data.map(transform).filter(validate); // Move this to service!
200
+ },
201
+ });
202
+ ```
203
+
204
+ ### Query Key = All Dependencies
205
+
206
+ ```typescript
207
+ // ✅ CORRECT: Key includes all params → auto refetch when changed
208
+ useQuery({
209
+ queryKey: queryKeys.users.list({ page, search, status }),
210
+ queryFn: () => userService.list({ page, search, status }),
211
+ });
212
+
213
+ // ❌ WRONG: Missing deps in key → stale data
214
+ useQuery({
215
+ queryKey: queryKeys.users.list(),
216
+ queryFn: () => userService.list({ page, search }), // Params not in key!
217
+ });
218
+ ```
219
+
220
+ ### Conditional Queries with `enabled`
221
+
222
+ ```typescript
223
+ // Fetch user first, then fetch user's posts
224
+ const { data: user } = useQuery({
225
+ queryKey: queryKeys.user,
226
+ queryFn: authService.me,
227
+ });
228
+
229
+ const { data: posts } = useQuery({
230
+ queryKey: queryKeys.posts.byUser(user?.id!),
231
+ queryFn: () => postService.listByUser(user!.id),
232
+ enabled: !!user, // ← Only runs when user exists
233
+ });
234
+ ```
235
+
236
+ ### Invalidate Correctly
237
+
238
+ ```typescript
239
+ // ✅ Invalidate by prefix (all user queries)
240
+ queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
241
+
242
+ // ✅ Invalidate specific query
243
+ queryClient.invalidateQueries({ queryKey: queryKeys.users.detail(id) });
244
+
245
+ // ❌ DON'T invalidate everything
246
+ queryClient.invalidateQueries(); // Too broad!
247
+
248
+ // ❌ DON'T refetch manually
249
+ await userService.create(data);
250
+ refetch(); // Wrong! Use invalidateQueries
251
+ ```
252
+
253
+ ### Optimistic Updates (Use Sparingly)
254
+
255
+ Only for instant feedback UX (like/unlike, toggle, drag-drop):
256
+
257
+ ```typescript
258
+ const likeMutation = useMutation({
259
+ mutationFn: postService.like,
260
+ onMutate: async (postId) => {
261
+ await queryClient.cancelQueries({ queryKey: queryKeys.posts.detail(postId) });
262
+ const previous = queryClient.getQueryData(queryKeys.posts.detail(postId));
263
+ queryClient.setQueryData(queryKeys.posts.detail(postId), (old: Post) => ({
264
+ ...old,
265
+ liked: true,
266
+ likesCount: old.likesCount + 1,
267
+ }));
268
+ return { previous };
269
+ },
270
+ onError: (err, postId, context) => {
271
+ queryClient.setQueryData(queryKeys.posts.detail(postId), context?.previous);
272
+ },
273
+ onSettled: (data, error, postId) => {
274
+ queryClient.invalidateQueries({ queryKey: queryKeys.posts.detail(postId) });
275
+ },
276
+ });
277
+ ```
278
+
279
+ ### Prefetching (For Better UX)
280
+
281
+ ```typescript
282
+ <Link
283
+ href={`/users/${user.id}`}
284
+ onMouseEnter={() => {
285
+ queryClient.prefetchQuery({
286
+ queryKey: queryKeys.users.detail(user.id),
287
+ queryFn: () => userService.get(user.id),
288
+ });
289
+ }}
290
+ >
291
+ {user.name}
292
+ </Link>
293
+ ```
294
+
295
+ ---
296
+
297
+ ## Common Mistakes
298
+
299
+ ```typescript
300
+ // ❌ Mixing server state with local state
301
+ const [users, setUsers] = useState([]); // DELETE THIS
302
+ const { data } = useQuery({...}); // USE THIS ONLY
303
+
304
+ // ❌ Fetching in useEffect
305
+ useEffect(() => {
306
+ fetchUsers().then(setUsers); // WRONG
307
+ }, []);
308
+ // ✅ Use useQuery instead
309
+
310
+ // ❌ Missing error handling in mutation
311
+ const mutation = useMutation({
312
+ mutationFn: userService.create,
313
+ onSuccess: () => message.success("Created"),
314
+ // MISSING: onError for form validation!
315
+ });
316
+
317
+ // ✅ Always handle errors
318
+ const mutation = useMutation({
319
+ mutationFn: userService.create,
320
+ onSuccess: () => message.success(t("created")),
321
+ onError: (error) => form.setFields(getFormErrors(error)),
322
+ });
323
+ ```
324
+
325
+ ---
326
+
327
+ ## When NOT to Use TanStack Query
328
+
329
+ ```typescript
330
+ // ❌ For client-only state (use useState or Zustand)
331
+ const [isModalOpen, setIsModalOpen] = useState(false);
332
+ const [selectedItems, setSelectedItems] = useState<number[]>([]);
333
+
334
+ // ❌ For derived/computed values (use useMemo)
335
+ const filteredUsers = useMemo(
336
+ () => users.filter(u => u.active),
337
+ [users]
338
+ );
339
+ ```