You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .claude/commands/you-might-not-need-a-callback.md
+25-8Lines changed: 25 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,17 +16,34 @@ User arguments: $ARGUMENTS
16
16
Read before analyzing:
17
17
1.https://react.dev/reference/react/useCallback — official docs on when useCallback is actually needed
18
18
19
+
## The one rule that matters
20
+
21
+
`useCallback` is only useful when **something observes the reference**. Ask: does anything care if this function gets a new identity on re-render?
22
+
23
+
Observers that care about reference stability:
24
+
- A `useEffect` that lists the function in its deps array
25
+
- A `useMemo` that lists the function in its deps array
26
+
- Another `useCallback` that lists the function in its deps array
27
+
- A child component wrapped in `React.memo` that receives the function as a prop
28
+
29
+
If none of those apply — if the function is only called inline, or passed to a non-memoized child, or assigned to a native element event — the reference is unobserved and `useCallback` adds overhead with zero benefit.
30
+
19
31
## Anti-patterns to detect
20
32
21
-
1.**useCallback on functions not passed as props or deps**: No benefit if only called within the same component.
22
-
2.**useCallback with deps that change every render**: Memoization is wasted.
23
-
3.**useCallback on handlers passed to native elements**: `<button onClick={fn}>` doesn't benefit from stable references.
24
-
4.**useCallback wrapping functions that return new objects/arrays**: Memoization at the wrong level.
25
-
5.**useCallback with empty deps when deps are needed**: Stale closures.
26
-
6.**Pairing useCallback + React.memo unnecessarily**: Only optimize when you've measured a problem.
27
-
7.**useCallback in hooks that don't need stable references**: Not every hook return needs memoization.
33
+
1.**No observer tracks the reference**: The function is only called inline in the same component, or passed to a non-memoized child, or used as a native element handler (`<button onClick={fn}>`). Nothing re-runs or bails out based on reference identity. Remove `useCallback`.
34
+
2.**useCallback with deps that change every render**: If a dep is a plain object/array created inline, or state that changes on every interaction, memoization buys nothing — the function gets a new identity anyway.
35
+
3.**useCallback on handlers passed only to native elements**: `<button onClick={fn}>` — React never does reference equality on native element props. No benefit.
36
+
4.**useCallback wrapping functions that return new objects/arrays**: Stable function identity, unstable return value — memoization is at the wrong level. Use `useMemo` on the return value instead, or restructure.
37
+
5.**useCallback with empty deps when deps are needed**: Stale closure — reads initial values forever. This is a correctness bug, not just a performance issue.
38
+
6.**Pairing useCallback + React.memo on trivially cheap renders**: If the child renders in < 1ms and re-renders rarely, the memo infrastructure costs more than it saves.
39
+
40
+
## Patterns that ARE correct — do not flag
28
41
29
-
Note: This codebase uses a ref pattern for stable callbacks (`useRef` + empty deps). That pattern is correct — don't flag it.
42
+
-`useCallback` whose result is in a `useEffect` dep array — prevents the effect from re-running on every render
43
+
-`useCallback` whose result is in a `useMemo` dep array — prevents the memo from recomputing on every render
44
+
-`useCallback` whose result is a dep of another `useCallback` — stabilises a callback chain
45
+
-`useCallback` passed to a `React.memo`-wrapped child — the whole point of the pattern
46
+
- This codebase's ref pattern: `useRef` + callback with empty deps that reads the ref inside — correct, do not flag
Copy file name to clipboardExpand all lines: .claude/rules/global.md
+27-3Lines changed: 27 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,10 @@
1
1
# Global Standards
2
2
3
3
## Logging
4
-
Import `createLogger` from `sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`.
4
+
Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`. Inside API routes wrapped with `withRouteHandler`, loggers automatically include the request ID.
5
+
6
+
## API Route Handlers
7
+
All API route handlers must be wrapped with `withRouteHandler` from `@/lib/core/utils/with-route-handler`. Never export a bare `async function GET/POST/...` — always use `export const METHOD = withRouteHandler(...)`.
5
8
6
9
## Comments
7
10
Use TSDoc for documentation. No `====` separators. No non-TSDoc comments.
@@ -10,7 +13,7 @@ Use TSDoc for documentation. No `====` separators. No non-TSDoc comments.
10
13
Never update global styles. Keep all styling local to components.
11
14
12
15
## ID Generation
13
-
Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@/lib/core/utils/uuid`:
16
+
Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@sim/utils/id`:
14
17
15
18
-`generateId()` — UUID v4, use by default
16
19
-`generateShortId(size?)` — short URL-safe ID (default 21 chars), for compact identifiers
@@ -24,11 +27,32 @@ import { v4 as uuidv4 } from 'uuid'
### NEVER use `mockAuth()`, `mockConsoleLogger()`, or `setupCommonApiMocks()` from `@sim/testing`
106
-
107
-
These helpers internally use `vi.doMock()` which is slow. Use direct `vi.hoisted()` + `vi.mock()` instead.
108
-
109
109
### Mock heavy transitive dependencies
110
110
111
111
If a module under test imports `@/blocks` (200+ files), `@/tools/registry`, or other heavy modules, mock them:
@@ -135,83 +135,129 @@ await new Promise(r => setTimeout(r, 1))
135
135
vi.useFakeTimers()
136
136
```
137
137
138
-
## Mock Pattern Reference
138
+
## Centralized Mocks (prefer over local declarations)
139
+
140
+
`@sim/testing` exports ready-to-use mock modules for common dependencies. Import and pass directly to `vi.mock()` — no `vi.hoisted()` boilerplate needed. Each paired `*MockFns` object exposes the underlying `vi.fn()`s for per-test overrides.
Only define a local `vi.mock('@/lib/auth', ...)` if the module under test consumes exports outside the centralized shape (e.g., `auth.api.verifyOneTimeToken`, `auth.api.resetPassword`).
-`db.transaction(cb)` calls cb with `dbChainMock.db`
226
+
227
+
`.for('update')` (Postgres row-level locking) is supported on `where`
228
+
builders. It returns a thenable with `.limit` / `.orderBy` / `.returning` /
229
+
`.groupBy` attached, so both `await .where().for('update')` (terminal) and
230
+
`await .where().for('update').limit(1)` (chained) work. Override the terminal
231
+
result with `dbChainMockFns.for.mockResolvedValueOnce([...])`; for the chained
232
+
form, mock the downstream terminal (e.g. `dbChainMockFns.limit.mockResolvedValueOnce([...])`).
233
+
234
+
All terminals default to `Promise.resolve([])`. Override per-test with `dbChainMockFns.<terminal>.mockResolvedValueOnce(...)`.
235
+
236
+
Use `resetDbChainMock()` in `beforeEach` only when tests replace wiring with `.mockReturnValue` / `.mockResolvedValue` (permanent). Tests using only `...Once` variants don't need it.
0 commit comments