@brookmind/ai-toolkit 1.1.7 → 1.2.0

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 (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +42 -14
  3. package/dist/__tests__/index.test.d.ts +2 -0
  4. package/dist/__tests__/index.test.d.ts.map +1 -0
  5. package/dist/__tests__/index.test.js +214 -0
  6. package/dist/__tests__/index.test.js.map +1 -0
  7. package/dist/constants.d.ts +10 -0
  8. package/dist/constants.d.ts.map +1 -0
  9. package/dist/constants.js +39 -0
  10. package/dist/constants.js.map +1 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +49 -332
  14. package/dist/index.js.map +1 -1
  15. package/dist/services/installers.d.ts +8 -0
  16. package/dist/services/installers.d.ts.map +1 -0
  17. package/dist/services/installers.js +79 -0
  18. package/dist/services/installers.js.map +1 -0
  19. package/dist/services/opencode.d.ts +3 -0
  20. package/dist/services/opencode.d.ts.map +1 -0
  21. package/dist/services/opencode.js +33 -0
  22. package/dist/services/opencode.js.map +1 -0
  23. package/dist/types.d.ts +10 -0
  24. package/dist/types.d.ts.map +1 -0
  25. package/dist/types.js +2 -0
  26. package/dist/types.js.map +1 -0
  27. package/dist/ui/categorize.d.ts +6 -0
  28. package/dist/ui/categorize.d.ts.map +1 -0
  29. package/dist/ui/categorize.js +69 -0
  30. package/dist/ui/categorize.js.map +1 -0
  31. package/dist/ui/choices.d.ts +6 -0
  32. package/dist/ui/choices.d.ts.map +1 -0
  33. package/dist/ui/choices.js +70 -0
  34. package/dist/ui/choices.js.map +1 -0
  35. package/dist/ui/display.d.ts +8 -0
  36. package/dist/ui/display.d.ts.map +1 -0
  37. package/dist/ui/display.js +84 -0
  38. package/dist/ui/display.js.map +1 -0
  39. package/dist/utils/fs.d.ts +5 -0
  40. package/dist/utils/fs.d.ts.map +1 -0
  41. package/dist/utils/fs.js +40 -0
  42. package/dist/utils/fs.js.map +1 -0
  43. package/dist/utils/terminal.d.ts +5 -0
  44. package/dist/utils/terminal.d.ts.map +1 -0
  45. package/dist/utils/terminal.js +18 -0
  46. package/dist/utils/terminal.js.map +1 -0
  47. package/package.json +27 -5
  48. package/agents/code-reviewer.md +0 -35
  49. package/agents/code-simplifier.md +0 -52
  50. package/commands/create-pr-description.md +0 -102
  51. package/commands/create-pr.md +0 -76
  52. package/commands/create-react-tests.md +0 -207
  53. package/mcps/context7/.mcp.json +0 -13
  54. package/mcps/expo-mcp/.mcp.json +0 -13
  55. package/mcps/figma-mcp/.mcp.json +0 -10
  56. package/skills/github-cli/SKILL.md +0 -125
  57. package/skills/pdf-processing-pro/FORMS.md +0 -610
  58. package/skills/pdf-processing-pro/OCR.md +0 -137
  59. package/skills/pdf-processing-pro/SKILL.md +0 -296
  60. package/skills/pdf-processing-pro/TABLES.md +0 -626
  61. package/skills/pdf-processing-pro/scripts/analyze_form.py +0 -307
  62. package/skills/react-best-practices/AGENTS.md +0 -915
  63. package/skills/react-best-practices/README.md +0 -127
  64. package/skills/react-best-practices/SKILL.md +0 -110
  65. package/skills/react-best-practices/metadata.json +0 -14
  66. package/skills/react-best-practices/rules/_sections.md +0 -41
  67. package/skills/react-best-practices/rules/_template.md +0 -28
  68. package/skills/react-best-practices/rules/advanced-event-handler-refs.md +0 -80
  69. package/skills/react-best-practices/rules/advanced-use-latest.md +0 -76
  70. package/skills/react-best-practices/rules/async-defer-await.md +0 -80
  71. package/skills/react-best-practices/rules/async-dependencies.md +0 -36
  72. package/skills/react-best-practices/rules/async-parallel.md +0 -28
  73. package/skills/react-best-practices/rules/async-suspense-boundaries.md +0 -100
  74. package/skills/react-best-practices/rules/bundle-barrel-imports.md +0 -42
  75. package/skills/react-best-practices/rules/bundle-conditional.md +0 -106
  76. package/skills/react-best-practices/rules/bundle-preload.md +0 -44
  77. package/skills/react-best-practices/rules/client-event-listeners.md +0 -131
  78. package/skills/react-best-practices/rules/client-swr-dedup.md +0 -133
  79. package/skills/react-best-practices/rules/js-batch-dom-css.md +0 -82
  80. package/skills/react-best-practices/rules/js-cache-function-results.md +0 -80
  81. package/skills/react-best-practices/rules/js-cache-property-access.md +0 -28
  82. package/skills/react-best-practices/rules/js-cache-storage.md +0 -70
  83. package/skills/react-best-practices/rules/js-combine-iterations.md +0 -32
  84. package/skills/react-best-practices/rules/js-early-exit.md +0 -50
  85. package/skills/react-best-practices/rules/js-hoist-regexp.md +0 -45
  86. package/skills/react-best-practices/rules/js-index-maps.md +0 -37
  87. package/skills/react-best-practices/rules/js-length-check-first.md +0 -49
  88. package/skills/react-best-practices/rules/js-min-max-loop.md +0 -82
  89. package/skills/react-best-practices/rules/js-set-map-lookups.md +0 -24
  90. package/skills/react-best-practices/rules/js-tosorted-immutable.md +0 -57
  91. package/skills/react-best-practices/rules/rendering-activity.md +0 -90
  92. package/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -47
  93. package/skills/react-best-practices/rules/rendering-conditional-render.md +0 -40
  94. package/skills/react-best-practices/rules/rendering-content-visibility.md +0 -38
  95. package/skills/react-best-practices/rules/rendering-hoist-jsx.md +0 -65
  96. package/skills/react-best-practices/rules/rendering-svg-precision.md +0 -28
  97. package/skills/react-best-practices/rules/rerender-defer-reads.md +0 -39
  98. package/skills/react-best-practices/rules/rerender-dependencies.md +0 -45
  99. package/skills/react-best-practices/rules/rerender-derived-state.md +0 -29
  100. package/skills/react-best-practices/rules/rerender-functional-setstate.md +0 -74
  101. package/skills/react-best-practices/rules/rerender-lazy-state-init.md +0 -58
  102. package/skills/react-best-practices/rules/rerender-memo.md +0 -85
  103. package/skills/react-best-practices/rules/rerender-transitions.md +0 -40
  104. package/skills/skill-creator/LICENSE.txt +0 -202
  105. package/skills/skill-creator/SKILL.md +0 -209
  106. package/skills/skill-creator/scripts/init_skill.py +0 -303
  107. package/skills/skill-creator/scripts/package_skill.py +0 -110
  108. package/skills/skill-creator/scripts/quick_validate.py +0 -65
  109. package/skills/spring-boot-development/EXAMPLES.md +0 -2346
  110. package/skills/spring-boot-development/README.md +0 -595
  111. package/skills/spring-boot-development/SKILL.md +0 -1519
  112. package/themes/README.md +0 -68
  113. package/themes/claude-vivid.json +0 -72
@@ -1,100 +0,0 @@
1
- ---
2
- title: Strategic Suspense Boundaries
3
- impact: HIGH
4
- impactDescription: faster initial paint
5
- tags: async, suspense, layout-shift, react-19
6
- ---
7
-
8
- ## Strategic Suspense Boundaries
9
-
10
- Use Suspense boundaries to show wrapper UI immediately while data loads asynchronously. This improves perceived performance by not blocking the entire UI.
11
-
12
- **Incorrect (entire component waits for data):**
13
-
14
- ```tsx
15
- function Page() {
16
- const { data, isLoading } = useQuery(["data"], fetchData);
17
-
18
- if (isLoading) return <Skeleton />;
19
-
20
- return (
21
- <div>
22
- <div>Sidebar</div>
23
- <div>Header</div>
24
- <div>
25
- <DataDisplay data={data} />
26
- </div>
27
- <div>Footer</div>
28
- </div>
29
- );
30
- }
31
- ```
32
-
33
- The entire layout waits for data even though only the middle section needs it.
34
-
35
- **Correct (wrapper shows immediately, data section has its own loading state):**
36
-
37
- ```tsx
38
- function Page() {
39
- return (
40
- <div>
41
- <div>Sidebar</div>
42
- <div>Header</div>
43
- <div>
44
- <Suspense fallback={<Skeleton />}>
45
- <DataDisplay />
46
- </Suspense>
47
- </div>
48
- <div>Footer</div>
49
- </div>
50
- );
51
- }
52
-
53
- function DataDisplay() {
54
- const { data } = useSuspenseQuery(["data"], fetchData);
55
- return <div>{data.content}</div>;
56
- }
57
- ```
58
-
59
- Sidebar, Header, and Footer render immediately. Only DataDisplay shows a skeleton while loading.
60
-
61
- **Alternative with React 19 use() hook (share promise across components):**
62
-
63
- ```tsx
64
- function Page() {
65
- // Start fetch immediately, but don't await
66
- const dataPromise = useMemo(() => fetchData(), []);
67
-
68
- return (
69
- <div>
70
- <div>Sidebar</div>
71
- <div>Header</div>
72
- <Suspense fallback={<Skeleton />}>
73
- <DataDisplay dataPromise={dataPromise} />
74
- <DataSummary dataPromise={dataPromise} />
75
- </Suspense>
76
- <div>Footer</div>
77
- </div>
78
- );
79
- }
80
-
81
- function DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {
82
- const data = use(dataPromise); // React 19: unwraps the promise
83
- return <div>{data.content}</div>;
84
- }
85
-
86
- function DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {
87
- const data = use(dataPromise); // Reuses the same promise
88
- return <div>{data.summary}</div>;
89
- }
90
- ```
91
-
92
- Both components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.
93
-
94
- **When NOT to use this pattern:**
95
-
96
- - Critical data needed for layout decisions (affects positioning)
97
- - Small, fast queries where suspense overhead isn't worth it
98
- - When you want to avoid layout shift (loading → content jump)
99
-
100
- **Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.
@@ -1,42 +0,0 @@
1
- ---
2
- title: Avoid Barrel File Imports
3
- impact: CRITICAL
4
- impactDescription: 200-800ms import cost, slow builds
5
- tags: bundle, imports, tree-shaking, barrel-files, performance
6
- ---
7
-
8
- ## Avoid Barrel File Imports
9
-
10
- Import directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`).
11
-
12
- Popular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts.
13
-
14
- **Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph.
15
-
16
- **Incorrect (imports entire library):**
17
-
18
- ```tsx
19
- import { Check, X, Menu } from "lucide-react";
20
- // Loads 1,583 modules, takes ~2.8s extra in dev
21
- // Runtime cost: 200-800ms on every cold start
22
-
23
- import { Button, TextField } from "@mui/material";
24
- // Loads 2,225 modules, takes ~4.2s extra in dev
25
- ```
26
-
27
- **Correct (imports only what you need):**
28
-
29
- ```tsx
30
- import Check from "lucide-react/dist/esm/icons/check";
31
- import X from "lucide-react/dist/esm/icons/x";
32
- import Menu from "lucide-react/dist/esm/icons/menu";
33
- // Loads only 3 modules (~2KB vs ~1MB)
34
-
35
- import Button from "@mui/material/Button";
36
- import TextField from "@mui/material/TextField";
37
- // Loads only what you use
38
- ```
39
-
40
- Direct imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.
41
-
42
- Libraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`.
@@ -1,106 +0,0 @@
1
- ---
2
- title: Conditional Module Loading
3
- impact: HIGH
4
- impactDescription: loads large data only when needed
5
- tags: bundle, conditional-loading, lazy-loading, react-19, suspense
6
- ---
7
-
8
- ## Conditional Module Loading
9
-
10
- Load large data or modules only when a feature is activated, reducing initial bundle size.
11
-
12
- **React 19 with use() and Suspense (Recommended):**
13
-
14
- ```tsx
15
- import { use, Suspense, useMemo } from "react";
16
-
17
- function AnimationPlayer({ enabled }: { enabled: boolean }) {
18
- // Create promise only when enabled
19
- const framesPromise = useMemo(
20
- () =>
21
- enabled ? import("./animation-frames.js").then((m) => m.frames) : null,
22
- [enabled],
23
- );
24
-
25
- if (!framesPromise) return null;
26
-
27
- const frames = use(framesPromise); // React 19: unwraps promise
28
- return <Canvas frames={frames} />;
29
- }
30
-
31
- // Wrap in Suspense
32
- function AnimationSection({ enabled }: { enabled: boolean }) {
33
- return (
34
- <Suspense fallback={<Skeleton />}>
35
- <AnimationPlayer enabled={enabled} />
36
- </Suspense>
37
- );
38
- }
39
- ```
40
-
41
- **With React.lazy for component splitting:**
42
-
43
- ```tsx
44
- import { lazy, Suspense } from "react";
45
-
46
- // Component is only loaded when rendered
47
- const HeavyEditor = lazy(() => import("./HeavyEditor"));
48
-
49
- function EditorSection({ showEditor }: { showEditor: boolean }) {
50
- if (!showEditor) return null;
51
-
52
- return (
53
- <Suspense fallback={<EditorSkeleton />}>
54
- <HeavyEditor />
55
- </Suspense>
56
- );
57
- }
58
- ```
59
-
60
- **Traditional pattern with useEffect (pre-React 19):**
61
-
62
- ```tsx
63
- function AnimationPlayer({ enabled }: { enabled: boolean }) {
64
- const [frames, setFrames] = useState<Frame[] | null>(null);
65
- const [error, setError] = useState<Error | null>(null);
66
-
67
- useEffect(() => {
68
- if (enabled && !frames) {
69
- import("./animation-frames.js")
70
- .then((mod) => setFrames(mod.frames))
71
- .catch(setError);
72
- }
73
- }, [enabled, frames]);
74
-
75
- if (error) return <ErrorMessage error={error} />;
76
- if (!frames) return <Skeleton />;
77
- return <Canvas frames={frames} />;
78
- }
79
- ```
80
-
81
- **With React Query for data modules:**
82
-
83
- ```tsx
84
- import { useQuery } from "@tanstack/react-query";
85
-
86
- function AnimationPlayer({ enabled }: { enabled: boolean }) {
87
- const { data: frames, isLoading } = useQuery({
88
- queryKey: ["animation-frames"],
89
- queryFn: () => import("./animation-frames.js").then((m) => m.frames),
90
- enabled, // Only fetch when enabled
91
- });
92
-
93
- if (!enabled) return null;
94
- if (isLoading) return <Skeleton />;
95
- return <Canvas frames={frames} />;
96
- }
97
- ```
98
-
99
- **Key benefits:**
100
-
101
- - Reduces initial bundle size
102
- - Improves Time to Interactive (TTI)
103
- - Loads resources on-demand
104
- - Works with code splitting
105
-
106
- Reference: [React lazy](https://react.dev/reference/react/lazy)
@@ -1,44 +0,0 @@
1
- ---
2
- title: Preload Based on User Intent
3
- impact: MEDIUM
4
- impactDescription: reduces perceived latency
5
- tags: bundle, preload, user-intent, hover
6
- ---
7
-
8
- ## Preload Based on User Intent
9
-
10
- Preload heavy bundles before they're needed to reduce perceived latency.
11
-
12
- **Example (preload on hover/focus):**
13
-
14
- ```tsx
15
- function EditorButton({ onClick }: { onClick: () => void }) {
16
- const preload = () => {
17
- void import("./monaco-editor");
18
- };
19
-
20
- return (
21
- <button onMouseEnter={preload} onFocus={preload} onClick={onClick}>
22
- Open Editor
23
- </button>
24
- );
25
- }
26
- ```
27
-
28
- **Example (preload when feature flag is enabled):**
29
-
30
- ```tsx
31
- function FlagsProvider({ children, flags }: Props) {
32
- useEffect(() => {
33
- if (flags.editorEnabled) {
34
- void import("./monaco-editor").then((mod) => mod.init());
35
- }
36
- }, [flags.editorEnabled]);
37
-
38
- return (
39
- <FlagsContext.Provider value={flags}>{children}</FlagsContext.Provider>
40
- );
41
- }
42
- ```
43
-
44
- Preloading on user intent (hover, focus) or feature activation reduces perceived latency when the user actually clicks.
@@ -1,131 +0,0 @@
1
- ---
2
- title: Deduplicate Global Event Listeners
3
- impact: MEDIUM
4
- impactDescription: single listener for N components
5
- tags: client, event-listeners, useSyncExternalStore, subscription
6
- ---
7
-
8
- ## Deduplicate Global Event Listeners
9
-
10
- Use `useSyncExternalStore` to share global event listeners across component instances. This is the React 18+ standard pattern for subscribing to external stores.
11
-
12
- **Incorrect (N instances = N listeners):**
13
-
14
- ```tsx
15
- function useKeyboardShortcut(key: string, callback: () => void) {
16
- useEffect(() => {
17
- const handler = (e: KeyboardEvent) => {
18
- if (e.metaKey && e.key === key) {
19
- callback();
20
- }
21
- };
22
- window.addEventListener("keydown", handler);
23
- return () => window.removeEventListener("keydown", handler);
24
- }, [key, callback]);
25
- }
26
- ```
27
-
28
- When using `useKeyboardShortcut` multiple times, each instance registers a new listener.
29
-
30
- **Correct (N instances = 1 listener with useSyncExternalStore):**
31
-
32
- ```tsx
33
- import { useSyncExternalStore, useCallback, useEffect } from "react";
34
-
35
- // Module-level store for keyboard shortcuts
36
- const keyCallbacks = new Map<string, Set<() => void>>();
37
- const listeners = new Set<() => void>();
38
-
39
- function subscribeToKeyboard(onStoreChange: () => void) {
40
- if (listeners.size === 0) {
41
- // First subscriber - add the global listener
42
- window.addEventListener("keydown", handleKeyDown);
43
- }
44
- listeners.add(onStoreChange);
45
-
46
- return () => {
47
- listeners.delete(onStoreChange);
48
- if (listeners.size === 0) {
49
- // Last subscriber - remove the global listener
50
- window.removeEventListener("keydown", handleKeyDown);
51
- }
52
- };
53
- }
54
-
55
- function handleKeyDown(e: KeyboardEvent) {
56
- if (e.metaKey && keyCallbacks.has(e.key)) {
57
- keyCallbacks.get(e.key)!.forEach((cb) => cb());
58
- }
59
- }
60
-
61
- function useKeyboardShortcut(key: string, callback: () => void) {
62
- // Register callback in the module-level Map
63
- useEffect(() => {
64
- if (!keyCallbacks.has(key)) {
65
- keyCallbacks.set(key, new Set());
66
- }
67
- keyCallbacks.get(key)!.add(callback);
68
-
69
- return () => {
70
- const set = keyCallbacks.get(key);
71
- if (set) {
72
- set.delete(callback);
73
- if (set.size === 0) {
74
- keyCallbacks.delete(key);
75
- }
76
- }
77
- };
78
- }, [key, callback]);
79
-
80
- // Subscribe to the shared keyboard listener
81
- useSyncExternalStore(
82
- subscribeToKeyboard,
83
- () => null, // getSnapshot - we don't need state, just subscription
84
- () => null, // getServerSnapshot
85
- );
86
- }
87
-
88
- // Usage - multiple shortcuts share single listener
89
- function Profile() {
90
- useKeyboardShortcut("p", () => openProfile());
91
- useKeyboardShortcut("k", () => openSearch());
92
- useKeyboardShortcut("/", () => focusCommandBar());
93
- // All share ONE keydown listener!
94
- }
95
- ```
96
-
97
- **Simpler pattern for single-use subscriptions:**
98
-
99
- ```tsx
100
- import { useSyncExternalStore } from "react";
101
-
102
- function useOnlineStatus() {
103
- return useSyncExternalStore(
104
- (callback) => {
105
- window.addEventListener("online", callback);
106
- window.addEventListener("offline", callback);
107
- return () => {
108
- window.removeEventListener("online", callback);
109
- window.removeEventListener("offline", callback);
110
- };
111
- },
112
- () => navigator.onLine,
113
- () => true, // Server snapshot
114
- );
115
- }
116
-
117
- function StatusIndicator() {
118
- const isOnline = useOnlineStatus();
119
- return <span>{isOnline ? "🟢" : "🔴"}</span>;
120
- }
121
- ```
122
-
123
- **Why useSyncExternalStore?**
124
-
125
- - Official React API for external subscriptions
126
- - Handles concurrent rendering correctly
127
- - Automatic cleanup on unmount
128
- - Works with React 18+ concurrent features
129
- - No external dependencies required
130
-
131
- Reference: [React useSyncExternalStore](https://react.dev/reference/react/useSyncExternalStore)
@@ -1,133 +0,0 @@
1
- ---
2
- title: Use React Query for Automatic Deduplication
3
- impact: MEDIUM-HIGH
4
- impactDescription: automatic deduplication and caching
5
- tags: client, react-query, tanstack-query, deduplication, data-fetching
6
- ---
7
-
8
- ## Use React Query for Automatic Deduplication
9
-
10
- TanStack Query (React Query 5) enables request deduplication, caching, background refetching, and stale-while-revalidate patterns across component instances.
11
-
12
- **Incorrect (no deduplication, each instance fetches):**
13
-
14
- ```tsx
15
- function UserList() {
16
- const [users, setUsers] = useState<User[]>([]);
17
- const [isLoading, setIsLoading] = useState(true);
18
-
19
- useEffect(() => {
20
- fetch("/api/users")
21
- .then((r) => r.json())
22
- .then(setUsers)
23
- .finally(() => setIsLoading(false));
24
- }, []);
25
-
26
- if (isLoading) return <Skeleton />;
27
- return (
28
- <ul>
29
- {users.map((u) => (
30
- <li key={u.id}>{u.name}</li>
31
- ))}
32
- </ul>
33
- );
34
- }
35
- ```
36
-
37
- Multiple instances of `UserList` = multiple network requests.
38
-
39
- **Correct (multiple instances share one request):**
40
-
41
- ```tsx
42
- import { useQuery } from "@tanstack/react-query";
43
-
44
- function UserList() {
45
- const { data: users, isLoading } = useQuery({
46
- queryKey: ["users"],
47
- queryFn: () => fetch("/api/users").then((r) => r.json()),
48
- });
49
-
50
- if (isLoading) return <Skeleton />;
51
- return (
52
- <ul>
53
- {users.map((u) => (
54
- <li key={u.id}>{u.name}</li>
55
- ))}
56
- </ul>
57
- );
58
- }
59
- ```
60
-
61
- Multiple instances = single request, shared cache.
62
-
63
- **With Suspense (React 19):**
64
-
65
- ```tsx
66
- import { useSuspenseQuery } from "@tanstack/react-query";
67
-
68
- function UserList() {
69
- const { data: users } = useSuspenseQuery({
70
- queryKey: ["users"],
71
- queryFn: fetchUsers,
72
- });
73
-
74
- return (
75
- <ul>
76
- {users.map((u) => (
77
- <li key={u.id}>{u.name}</li>
78
- ))}
79
- </ul>
80
- );
81
- }
82
-
83
- // Parent component
84
- <Suspense fallback={<Skeleton />}>
85
- <UserList />
86
- </Suspense>;
87
- ```
88
-
89
- **For mutations:**
90
-
91
- ```tsx
92
- import { useMutation, useQueryClient } from "@tanstack/react-query";
93
-
94
- function UpdateButton({ userId }: { userId: string }) {
95
- const queryClient = useQueryClient();
96
-
97
- const { mutate, isPending } = useMutation({
98
- mutationFn: (data: UserUpdate) => updateUser(userId, data),
99
- onSuccess: () => {
100
- // Invalidate and refetch
101
- queryClient.invalidateQueries({ queryKey: ["users"] });
102
- },
103
- });
104
-
105
- return (
106
- <button onClick={() => mutate({ name: "New Name" })} disabled={isPending}>
107
- {isPending ? "Updating..." : "Update"}
108
- </button>
109
- );
110
- }
111
- ```
112
-
113
- **For immutable/static data:**
114
-
115
- ```tsx
116
- const { data } = useQuery({
117
- queryKey: ["config"],
118
- queryFn: fetchConfig,
119
- staleTime: Infinity, // Never refetch
120
- gcTime: Infinity, // Never garbage collect
121
- });
122
- ```
123
-
124
- **Key benefits:**
125
-
126
- - Automatic request deduplication
127
- - Built-in caching with configurable stale times
128
- - Background refetching
129
- - Optimistic updates
130
- - Suspense support (React 19)
131
- - DevTools for debugging
132
-
133
- Reference: [https://tanstack.com/query](https://tanstack.com/query)
@@ -1,82 +0,0 @@
1
- ---
2
- title: Batch DOM CSS Changes
3
- impact: MEDIUM
4
- impactDescription: reduces reflows/repaints
5
- tags: javascript, dom, css, performance, reflow
6
- ---
7
-
8
- ## Batch DOM CSS Changes
9
-
10
- Avoid changing styles one property at a time. Group multiple CSS changes together via classes or `cssText` to minimize browser reflows.
11
-
12
- **Incorrect (multiple reflows):**
13
-
14
- ```typescript
15
- function updateElementStyles(element: HTMLElement) {
16
- // Each line triggers a reflow
17
- element.style.width = '100px'
18
- element.style.height = '200px'
19
- element.style.backgroundColor = 'blue'
20
- element.style.border = '1px solid black'
21
- }
22
- ```
23
-
24
- **Correct (add class - single reflow):**
25
-
26
- ```typescript
27
- // CSS file
28
- .highlighted-box {
29
- width: 100px;
30
- height: 200px;
31
- background-color: blue;
32
- border: 1px solid black;
33
- }
34
-
35
- // JavaScript
36
- function updateElementStyles(element: HTMLElement) {
37
- element.classList.add('highlighted-box')
38
- }
39
- ```
40
-
41
- **Correct (change cssText - single reflow):**
42
-
43
- ```typescript
44
- function updateElementStyles(element: HTMLElement) {
45
- element.style.cssText = `
46
- width: 100px;
47
- height: 200px;
48
- background-color: blue;
49
- border: 1px solid black;
50
- `
51
- }
52
- ```
53
-
54
- **React example:**
55
-
56
- ```tsx
57
- // Incorrect: changing styles one by one
58
- function Box({ isHighlighted }: { isHighlighted: boolean }) {
59
- const ref = useRef<HTMLDivElement>(null)
60
-
61
- useEffect(() => {
62
- if (ref.current && isHighlighted) {
63
- ref.current.style.width = '100px'
64
- ref.current.style.height = '200px'
65
- ref.current.style.backgroundColor = 'blue'
66
- }
67
- }, [isHighlighted])
68
-
69
- return <div ref={ref}>Content</div>
70
- }
71
-
72
- // Correct: toggle class
73
- function Box({ isHighlighted }: { isHighlighted: boolean }) {
74
- return (
75
- <div className={isHighlighted ? 'highlighted-box' : ''}>
76
- Content
77
- </div>
78
- )
79
- }
80
- ```
81
-
82
- Prefer CSS classes over inline styles when possible. Classes are cached by the browser and provide better separation of concerns.