@reasonabletech/eslint-config 0.1.0 → 0.2.1

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.
@@ -0,0 +1,411 @@
1
+ # Refactoring React Code for Type Safety
2
+
3
+ A comprehensive guide to updating React components to work with strict TypeScript ESLint rules, focusing on the `@typescript-eslint/strict-boolean-expressions` rule.
4
+
5
+ ## Overview
6
+
7
+ This tutorial walks you through refactoring common React patterns that violate strict boolean expression rules. These patterns can cause subtle rendering bugs and inconsistent behavior.
8
+
9
+ ## The Problem
10
+
11
+ With `@typescript-eslint/strict-boolean-expressions` enabled, React code that uses "truthiness" checks will produce lint errors. These patterns are problematic because they can render unexpected values like `0`, empty strings, or `[object Object]` instead of nothing.
12
+
13
+ ## Common Problematic Patterns
14
+
15
+ ### 1. Object Truthiness Checks
16
+
17
+ **❌ Problematic Pattern:**
18
+
19
+ ```tsx
20
+ {
21
+ result.symbolIndex && <SymbolSummary result={result} />;
22
+ }
23
+
24
+ {
25
+ streamingMessage && <StreamingMessage message={streamingMessage} />;
26
+ }
27
+ ```
28
+
29
+ **✅ Fixed Pattern:**
30
+
31
+ ```tsx
32
+ // Explicit null/undefined checks
33
+ {
34
+ result.symbolIndex != null && <SymbolSummary result={result} />;
35
+ }
36
+ {
37
+ streamingMessage != null && <StreamingMessage message={streamingMessage} />;
38
+ }
39
+ ```
40
+
41
+ ### 2. Error Object Rendering
42
+
43
+ **❌ Problematic Pattern:**
44
+
45
+ ```tsx
46
+ {
47
+ error && <ErrorMessage>{error}</ErrorMessage>;
48
+ }
49
+
50
+ {
51
+ status.error && <Text color="red"> - {status.error}</Text>;
52
+ }
53
+ ```
54
+
55
+ **✅ Fixed Pattern:**
56
+
57
+ ```tsx
58
+ // Explicit error checks
59
+ {
60
+ error != null && <ErrorMessage>{error}</ErrorMessage>;
61
+ }
62
+ {
63
+ status.error != null && <Text color="red"> - {status.error}</Text>;
64
+ }
65
+
66
+ // Even better - handle error types properly
67
+ {
68
+ error instanceof Error && <ErrorMessage>{error.message}</ErrorMessage>;
69
+ }
70
+ ```
71
+
72
+ ### 3. Boolean State Checks
73
+
74
+ **❌ Problematic Pattern:**
75
+
76
+ ```tsx
77
+ {
78
+ isDevelopmentBypass && <DevelopmentBanner />;
79
+ }
80
+
81
+ {
82
+ isRetrying && <Spinner />;
83
+ }
84
+ ```
85
+
86
+ **✅ Fixed Pattern:**
87
+
88
+ ```tsx
89
+ // These are actually already correct! Boolean checks are allowed
90
+ {
91
+ isDevelopmentBypass && <DevelopmentBanner />;
92
+ }
93
+ {
94
+ isRetrying && <Spinner />;
95
+ }
96
+
97
+ // But for clarity, you can be explicit
98
+ {
99
+ isDevelopmentBypass === true && <DevelopmentBanner />;
100
+ }
101
+ {
102
+ isRetrying === true && <Spinner />;
103
+ }
104
+ ```
105
+
106
+ ### 4. String and Number Checks
107
+
108
+ **❌ Problematic Pattern:**
109
+
110
+ ```tsx
111
+ // Common patterns that would fail
112
+ {
113
+ count && <Counter value={count} />;
114
+ } // Could render 0
115
+ {
116
+ user?.name && <UserName name={user.name} />;
117
+ } // Could render empty string
118
+ {
119
+ items && <ItemList items={items} />;
120
+ } // Could render empty array
121
+ ```
122
+
123
+ **✅ Fixed Pattern:**
124
+
125
+ ```tsx
126
+ // Numbers - explicit comparison
127
+ {
128
+ count > 0 && <Counter value={count} />;
129
+ }
130
+ {
131
+ (count ?? 0) > 0 && <Counter value={count} />;
132
+ }
133
+
134
+ // Strings - explicit checks
135
+ {
136
+ user?.name != null && user.name.length > 0 && <UserName name={user.name} />;
137
+ }
138
+ {
139
+ Boolean(user?.name) && <UserName name={user.name} />;
140
+ }
141
+
142
+ // Arrays - check length
143
+ {
144
+ items.length > 0 && <ItemList items={items} />;
145
+ }
146
+ {
147
+ Array.isArray(items) && items.length > 0 && <ItemList items={items} />;
148
+ }
149
+ ```
150
+
151
+ ## Step-by-Step Refactoring Process
152
+
153
+ ### Step 1: Identify Problematic Patterns
154
+
155
+ Run ESLint to find violations:
156
+
157
+ ```bash
158
+ # In your project directory
159
+ pnpm lint
160
+
161
+ # Look for errors like:
162
+ # "Unexpected value in conditional. A boolean expression is required."
163
+ ```
164
+
165
+ ### Step 2: Categorize the Issues
166
+
167
+ Group violations by type:
168
+
169
+ - **Object checks**: `someObject && <Component />`
170
+ - **Array checks**: `someArray && <Component />`
171
+ - **Number checks**: `count && <Component />`
172
+ - **String checks**: `text && <Component />`
173
+ - **Error objects**: `error && <ErrorDisplay />`
174
+
175
+ ### Step 3: Apply Appropriate Fixes
176
+
177
+ #### For Object/Value Existence Checks:
178
+
179
+ ```tsx
180
+ // Before
181
+ {
182
+ user && <UserProfile user={user} />;
183
+ }
184
+
185
+ // After
186
+ {
187
+ user != null && <UserProfile user={user} />;
188
+ }
189
+ ```
190
+
191
+ #### For Array Checks:
192
+
193
+ ```tsx
194
+ // Before
195
+ {
196
+ items && <ItemList items={items} />;
197
+ }
198
+
199
+ // After
200
+ {
201
+ items.length > 0 && <ItemList items={items} />;
202
+ }
203
+ ```
204
+
205
+ #### For Numeric Checks:
206
+
207
+ ```tsx
208
+ // Before
209
+ {
210
+ count && <Counter count={count} />;
211
+ }
212
+
213
+ // After - depends on intent
214
+ {
215
+ count > 0 && <Counter count={count} />;
216
+ } // Only positive numbers
217
+ {
218
+ count != null && <Counter count={count} />;
219
+ } // Any number including 0
220
+ {
221
+ count !== 0 && <Counter count={count} />;
222
+ } // Any number except 0
223
+ ```
224
+
225
+ #### For String Checks:
226
+
227
+ ```tsx
228
+ // Before
229
+ {
230
+ message && <Alert>{message}</Alert>;
231
+ }
232
+
233
+ // After - depends on intent
234
+ {
235
+ message != null && message.length > 0 && <Alert>{message}</Alert>;
236
+ }
237
+ {
238
+ Boolean(message) && <Alert>{message}</Alert>;
239
+ }
240
+ {
241
+ message != null && <Alert>{message}</Alert>;
242
+ } // Allow empty strings
243
+ ```
244
+
245
+ ### Step 4: Handle Complex Conditions
246
+
247
+ For complex conditions, break them down or use helper functions:
248
+
249
+ ```tsx
250
+ // Complex condition
251
+ {
252
+ user && user.permissions && user.permissions.length > 0 && user.isActive && (
253
+ <AdminPanel user={user} />
254
+ );
255
+ }
256
+
257
+ // Better approach - use helper
258
+ const canShowAdminPanel = (user: User | null): boolean => {
259
+ return (
260
+ user != null &&
261
+ user.permissions != null &&
262
+ user.permissions.length > 0 &&
263
+ user.isActive === true
264
+ );
265
+ };
266
+
267
+ // In component
268
+ {
269
+ canShowAdminPanel(user) && <AdminPanel user={user} />;
270
+ }
271
+ ```
272
+
273
+ ## Advanced Refactoring Techniques
274
+
275
+ ### Custom Hooks for State Logic
276
+
277
+ Instead of complex conditions in JSX, use custom hooks:
278
+
279
+ ```tsx
280
+ function useComponentVisibility(
281
+ items: unknown[],
282
+ error: Error | null,
283
+ loading: boolean,
284
+ ) {
285
+ return {
286
+ shouldShowItems: items.length > 0 && !loading,
287
+ shouldShowError: error != null,
288
+ shouldShowLoading: loading === true,
289
+ shouldShowEmpty: items.length === 0 && !loading && error == null,
290
+ };
291
+ }
292
+
293
+ // In component
294
+ const { shouldShowItems, shouldShowError, shouldShowLoading, shouldShowEmpty } =
295
+ useComponentVisibility(items, error, loading);
296
+
297
+ return (
298
+ <>
299
+ {shouldShowItems && <ItemList items={items} />}
300
+ {shouldShowError && <ErrorDisplay error={error} />}
301
+ {shouldShowLoading && <LoadingSpinner />}
302
+ {shouldShowEmpty && <EmptyState />}
303
+ </>
304
+ );
305
+ ```
306
+
307
+ ### Guard Components
308
+
309
+ For reusable conditional logic:
310
+
311
+ ```tsx
312
+ interface ConditionalProps {
313
+ condition: boolean;
314
+ children: React.ReactNode;
315
+ fallback?: React.ReactNode;
316
+ }
317
+
318
+ function Conditional({
319
+ condition,
320
+ children,
321
+ fallback = null,
322
+ }: ConditionalProps) {
323
+ return condition ? <>{children}</> : <>{fallback}</>;
324
+ }
325
+
326
+ // Usage
327
+ <Conditional condition={items.length > 0} fallback={<EmptyState />}>
328
+ <ItemList items={items} />
329
+ </Conditional>;
330
+ ```
331
+
332
+ ### Type Guards for Better Safety
333
+
334
+ Create type guards for complex object checks:
335
+
336
+ ```tsx
337
+ function hasSymbolIndex(
338
+ result: AnalysisResult,
339
+ ): result is AnalysisResult & { symbolIndex: SymbolIndex } {
340
+ return result.symbolIndex != null;
341
+ }
342
+
343
+ // In component
344
+ {
345
+ hasSymbolIndex(result) && <SymbolSummary result={result} />;
346
+ }
347
+ ```
348
+
349
+ ## Testing Refactored Components
350
+
351
+ After refactoring, ensure your components still work as expected:
352
+
353
+ ```tsx
354
+ describe("RefactoredComponent", () => {
355
+ it("renders content when conditions are met", () => {
356
+ render(<MyComponent items={["item1"]} error={null} loading={false} />);
357
+ expect(screen.getByTestId("item-list")).toBeInTheDocument();
358
+ });
359
+
360
+ it("renders empty state when no items", () => {
361
+ render(<MyComponent items={[]} error={null} loading={false} />);
362
+ expect(screen.getByTestId("empty-state")).toBeInTheDocument();
363
+ });
364
+
365
+ it("renders error when present", () => {
366
+ const error = new Error("Test error");
367
+ render(<MyComponent items={[]} error={error} loading={false} />);
368
+ expect(screen.getByTestId("error-display")).toBeInTheDocument();
369
+ });
370
+
371
+ it("does not render anything when count is 0", () => {
372
+ render(<Counter count={0} />);
373
+ expect(screen.queryByTestId("counter-display")).not.toBeInTheDocument();
374
+ });
375
+ });
376
+ ```
377
+
378
+ ## Implementation Checklist
379
+
380
+ - [ ] Run `pnpm lint` to identify all strict-boolean-expressions violations
381
+ - [ ] Group violations by pattern type (object, array, number, string, error)
382
+ - [ ] For each violation:
383
+ - [ ] Determine the intended behavior (show on truthy vs. show on non-null vs. show on non-empty)
384
+ - [ ] Apply appropriate explicit boolean check
385
+ - [ ] Test the component behavior with edge cases (0, "", null, undefined, empty arrays)
386
+ - [ ] Consider extracting complex conditions to helper functions or custom hooks
387
+ - [ ] Update tests to verify correct behavior with edge cases
388
+ - [ ] Run tests to ensure no regressions
389
+ - [ ] Run `pnpm lint` again to verify all violations are fixed
390
+
391
+ ## Benefits of This Approach
392
+
393
+ 1. **Prevents Rendering Bugs**: No more accidentally rendering `0`, `""`, or `[object Object]`
394
+ 2. **Explicit Intent**: Code clearly shows what conditions trigger rendering
395
+ 3. **Consistent Architecture**: Same strict patterns across TypeScript and React code
396
+ 4. **Better Maintainability**: Conditions are unambiguous and easy to understand
397
+ 5. **Type Safety**: Explicit checks work better with TypeScript's type system
398
+ 6. **AI Code Safety**: Strict checks prevent subtle bugs in generated code
399
+
400
+ ## Additional Resources
401
+
402
+ - [TypeScript ESLint strict-boolean-expressions rule](https://typescript-eslint.io/rules/strict-boolean-expressions/)
403
+ - [React Conditional Rendering Documentation](https://react.dev/learn/conditional-rendering)
404
+ - [Error Handling Standards](../../../../../docs/standards/error-handling.md)
405
+ - [TypeScript Standards](../../../../../docs/standards/typescript-standards.md)
406
+
407
+ ## Related Documentation
408
+
409
+ - [React Configuration](../reference/frameworks/react-config.md) — React ESLint configuration details
410
+ - [AI Code Safety](../concepts/ai-code-safety.md) — Why strict linting matters for AI-generated code
411
+ - [Usage Guide](../guides/usage-guide.md) — Setup instructions and troubleshooting
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reasonabletech/eslint-config",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Shared ESLint configuration",
5
5
  "keywords": [
6
6
  "reasonabletech",
@@ -79,7 +79,7 @@
79
79
  "tsup": "8.5.1",
80
80
  "typescript": "5.9.3",
81
81
  "vitest": "4.0.18",
82
- "@reasonabletech/config-typescript": "0.1.1"
82
+ "@reasonabletech/config-typescript": "0.1.2"
83
83
  },
84
84
  "license": "MIT",
85
85
  "repository": {
@@ -95,6 +95,7 @@
95
95
  "dist",
96
96
  "!dist/**/*.map",
97
97
  "!dist/**/*.tsbuildinfo",
98
+ "docs/**/*",
98
99
  "README.md",
99
100
  "CHANGELOG.md"
100
101
  ],