@0xsown/vibe-code-fe 1.0.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.
- package/bin/index.js +181 -0
- package/package.json +32 -0
- package/skills/claude-md-improver/SKILL.md +179 -0
- package/skills/claude-md-improver/references/quality-criteria.md +109 -0
- package/skills/claude-md-improver/references/templates.md +253 -0
- package/skills/claude-md-improver/references/update-guidelines.md +150 -0
- package/skills/find-skills/SKILL.md +133 -0
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/SKILL.md +42 -0
- package/skills/next-best-practices/SKILL.md +153 -0
- package/skills/next-best-practices/async-patterns.md +87 -0
- package/skills/next-best-practices/bundling.md +180 -0
- package/skills/next-best-practices/data-patterns.md +297 -0
- package/skills/next-best-practices/debug-tricks.md +105 -0
- package/skills/next-best-practices/directives.md +73 -0
- package/skills/next-best-practices/error-handling.md +227 -0
- package/skills/next-best-practices/file-conventions.md +140 -0
- package/skills/next-best-practices/font.md +245 -0
- package/skills/next-best-practices/functions.md +108 -0
- package/skills/next-best-practices/hydration-error.md +91 -0
- package/skills/next-best-practices/image.md +173 -0
- package/skills/next-best-practices/metadata.md +301 -0
- package/skills/next-best-practices/parallel-routes.md +287 -0
- package/skills/next-best-practices/route-handlers.md +146 -0
- package/skills/next-best-practices/rsc-boundaries.md +159 -0
- package/skills/next-best-practices/runtime-selection.md +39 -0
- package/skills/next-best-practices/scripts.md +141 -0
- package/skills/next-best-practices/self-hosting.md +371 -0
- package/skills/next-best-practices/suspense-boundaries.md +67 -0
- package/skills/next-cache-components/SKILL.md +411 -0
- package/skills/shadcn-ui/README.md +248 -0
- package/skills/shadcn-ui/SKILL.md +326 -0
- package/skills/shadcn-ui/examples/auth-layout.tsx +177 -0
- package/skills/shadcn-ui/examples/data-table.tsx +313 -0
- package/skills/shadcn-ui/examples/form-pattern.tsx +177 -0
- package/skills/shadcn-ui/resources/component-catalog.md +481 -0
- package/skills/shadcn-ui/resources/customization-guide.md +516 -0
- package/skills/shadcn-ui/resources/migration-guide.md +463 -0
- package/skills/shadcn-ui/resources/setup-guide.md +412 -0
- package/skills/shadcn-ui/scripts/verify-setup.sh +134 -0
- package/skills/supabase-postgres-best-practices/AGENTS.md +68 -0
- package/skills/supabase-postgres-best-practices/CLAUDE.md +68 -0
- package/skills/supabase-postgres-best-practices/README.md +116 -0
- package/skills/supabase-postgres-best-practices/SKILL.md +64 -0
- package/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
- package/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
- package/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
- package/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
- package/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
- package/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
- package/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
- package/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
- package/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
- package/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
- package/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
- package/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
- package/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
- package/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
- package/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
- package/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
- package/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
- package/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
- package/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
- package/skills/supabase-postgres-best-practices/references/query-index-types.md +48 -0
- package/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
- package/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
- package/skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -0
- package/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
- package/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
- package/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
- package/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
- package/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
- package/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
- package/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
- package/skills/supabase-postgres-best-practices/references/security-rls-performance.md +57 -0
- package/skills/tailwind-design-system/SKILL.md +874 -0
- package/skills/vercel-composition-patterns/AGENTS.md +946 -0
- package/skills/vercel-composition-patterns/README.md +60 -0
- package/skills/vercel-composition-patterns/SKILL.md +89 -0
- package/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md +100 -0
- package/skills/vercel-composition-patterns/rules/architecture-compound-components.md +112 -0
- package/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md +87 -0
- package/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md +100 -0
- package/skills/vercel-composition-patterns/rules/react19-no-forwardref.md +42 -0
- package/skills/vercel-composition-patterns/rules/state-context-interface.md +191 -0
- package/skills/vercel-composition-patterns/rules/state-decouple-implementation.md +113 -0
- package/skills/vercel-composition-patterns/rules/state-lift-state.md +125 -0
- package/skills/vercel-react-best-practices/AGENTS.md +2934 -0
- package/skills/vercel-react-best-practices/README.md +123 -0
- package/skills/vercel-react-best-practices/SKILL.md +136 -0
- package/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
- package/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
- package/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
- package/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
- package/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
- package/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
- package/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
- package/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
- package/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
- package/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
- package/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
- package/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
- package/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
- package/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
- package/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
- package/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
- package/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
- package/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
- package/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
- package/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
- package/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
- package/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
- package/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
- package/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
- package/skills/vercel-react-native-skills/AGENTS.md +2897 -0
- package/skills/vercel-react-native-skills/README.md +165 -0
- package/skills/vercel-react-native-skills/SKILL.md +121 -0
- package/skills/vercel-react-native-skills/rules/animation-derived-value.md +53 -0
- package/skills/vercel-react-native-skills/rules/animation-gesture-detector-press.md +95 -0
- package/skills/vercel-react-native-skills/rules/animation-gpu-properties.md +65 -0
- package/skills/vercel-react-native-skills/rules/design-system-compound-components.md +66 -0
- package/skills/vercel-react-native-skills/rules/fonts-config-plugin.md +71 -0
- package/skills/vercel-react-native-skills/rules/imports-design-system-folder.md +68 -0
- package/skills/vercel-react-native-skills/rules/js-hoist-intl.md +61 -0
- package/skills/vercel-react-native-skills/rules/list-performance-callbacks.md +44 -0
- package/skills/vercel-react-native-skills/rules/list-performance-function-references.md +132 -0
- package/skills/vercel-react-native-skills/rules/list-performance-images.md +53 -0
- package/skills/vercel-react-native-skills/rules/list-performance-inline-objects.md +97 -0
- package/skills/vercel-react-native-skills/rules/list-performance-item-expensive.md +94 -0
- package/skills/vercel-react-native-skills/rules/list-performance-item-memo.md +82 -0
- package/skills/vercel-react-native-skills/rules/list-performance-item-types.md +104 -0
- package/skills/vercel-react-native-skills/rules/list-performance-virtualize.md +67 -0
- package/skills/vercel-react-native-skills/rules/monorepo-native-deps-in-app.md +46 -0
- package/skills/vercel-react-native-skills/rules/monorepo-single-dependency-versions.md +63 -0
- package/skills/vercel-react-native-skills/rules/navigation-native-navigators.md +188 -0
- package/skills/vercel-react-native-skills/rules/react-compiler-destructure-functions.md +50 -0
- package/skills/vercel-react-native-skills/rules/react-compiler-reanimated-shared-values.md +48 -0
- package/skills/vercel-react-native-skills/rules/react-state-dispatcher.md +91 -0
- package/skills/vercel-react-native-skills/rules/react-state-fallback.md +56 -0
- package/skills/vercel-react-native-skills/rules/react-state-minimize.md +65 -0
- package/skills/vercel-react-native-skills/rules/rendering-no-falsy-and.md +74 -0
- package/skills/vercel-react-native-skills/rules/rendering-text-in-text-component.md +36 -0
- package/skills/vercel-react-native-skills/rules/scroll-position-no-state.md +82 -0
- package/skills/vercel-react-native-skills/rules/state-ground-truth.md +80 -0
- package/skills/vercel-react-native-skills/rules/ui-expo-image.md +66 -0
- package/skills/vercel-react-native-skills/rules/ui-image-gallery.md +104 -0
- package/skills/vercel-react-native-skills/rules/ui-measure-views.md +78 -0
- package/skills/vercel-react-native-skills/rules/ui-menus.md +174 -0
- package/skills/vercel-react-native-skills/rules/ui-native-modals.md +77 -0
- package/skills/vercel-react-native-skills/rules/ui-pressable.md +61 -0
- package/skills/vercel-react-native-skills/rules/ui-safe-area-scroll.md +65 -0
- package/skills/vercel-react-native-skills/rules/ui-scrollview-content-inset.md +45 -0
- package/skills/vercel-react-native-skills/rules/ui-styling.md +87 -0
- package/skills/web-design-guidelines/SKILL.md +39 -0
- package/templates/AGENTS.md +31 -0
- package/templates/CLAUDE.md +31 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Hoist Intl Formatter Creation
|
|
3
|
+
impact: LOW-MEDIUM
|
|
4
|
+
impactDescription: avoids expensive object recreation
|
|
5
|
+
tags: javascript, intl, optimization, memoization
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Hoist Intl Formatter Creation
|
|
9
|
+
|
|
10
|
+
Don't create `Intl.DateTimeFormat`, `Intl.NumberFormat`, or
|
|
11
|
+
`Intl.RelativeTimeFormat` inside render or loops. These are expensive to
|
|
12
|
+
instantiate. Hoist to module scope when the locale/options are static.
|
|
13
|
+
|
|
14
|
+
**Incorrect (new formatter every render):**
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
function Price({ amount }: { amount: number }) {
|
|
18
|
+
const formatter = new Intl.NumberFormat('en-US', {
|
|
19
|
+
style: 'currency',
|
|
20
|
+
currency: 'USD',
|
|
21
|
+
})
|
|
22
|
+
return <Text>{formatter.format(amount)}</Text>
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Correct (hoisted to module scope):**
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
const currencyFormatter = new Intl.NumberFormat('en-US', {
|
|
30
|
+
style: 'currency',
|
|
31
|
+
currency: 'USD',
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
function Price({ amount }: { amount: number }) {
|
|
35
|
+
return <Text>{currencyFormatter.format(amount)}</Text>
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**For dynamic locales, memoize:**
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
const dateFormatter = useMemo(
|
|
43
|
+
() => new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }),
|
|
44
|
+
[locale]
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Common formatters to hoist:**
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
// Module-level formatters
|
|
52
|
+
const dateFormatter = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium' })
|
|
53
|
+
const timeFormatter = new Intl.DateTimeFormat('en-US', { timeStyle: 'short' })
|
|
54
|
+
const percentFormatter = new Intl.NumberFormat('en-US', { style: 'percent' })
|
|
55
|
+
const relativeFormatter = new Intl.RelativeTimeFormat('en-US', {
|
|
56
|
+
numeric: 'auto',
|
|
57
|
+
})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Creating `Intl` objects is significantly more expensive than `RegExp` or plain
|
|
61
|
+
objects—each instantiation parses locale data and builds internal lookup tables.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Hoist callbacks to the root of lists
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Fewer re-renders and faster lists
|
|
5
|
+
tags: tag1, tag2
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## List performance callbacks
|
|
9
|
+
|
|
10
|
+
**Impact: HIGH (Fewer re-renders and faster lists)**
|
|
11
|
+
|
|
12
|
+
When passing callback functions to list items, create a single instance of the
|
|
13
|
+
callback at the root of the list. Items should then call it with a unique
|
|
14
|
+
identifier.
|
|
15
|
+
|
|
16
|
+
**Incorrect (creates a new callback on each render):**
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
return (
|
|
20
|
+
<LegendList
|
|
21
|
+
renderItem={({ item }) => {
|
|
22
|
+
// bad: creates a new callback on each render
|
|
23
|
+
const onPress = () => handlePress(item.id)
|
|
24
|
+
return <Item key={item.id} item={item} onPress={onPress} />
|
|
25
|
+
}}
|
|
26
|
+
/>
|
|
27
|
+
)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Correct (a single function instance passed to each item):**
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
const onPress = useCallback(() => handlePress(item.id), [handlePress, item.id])
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<LegendList
|
|
37
|
+
renderItem={({ item }) => (
|
|
38
|
+
<Item key={item.id} item={item} onPress={onPress} />
|
|
39
|
+
)}
|
|
40
|
+
/>
|
|
41
|
+
)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Reference: [Link to documentation or resource](https://example.com)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Optimize List Performance with Stable Object References
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: virtualization relies on reference stability
|
|
5
|
+
tags: lists, performance, flatlist, virtualization
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Optimize List Performance with Stable Object References
|
|
9
|
+
|
|
10
|
+
Don't map or filter data before passing to virtualized lists. Virtualization
|
|
11
|
+
relies on object reference stability to know what changed—new references cause
|
|
12
|
+
full re-renders of all visible items. Attempt to prevent frequent renders at the
|
|
13
|
+
list-parent level.
|
|
14
|
+
|
|
15
|
+
Where needed, use context selectors within list items.
|
|
16
|
+
|
|
17
|
+
**Incorrect (creates new object references on every keystroke):**
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
function DomainSearch() {
|
|
21
|
+
const { keyword, setKeyword } = useKeywordZustandState()
|
|
22
|
+
const { data: tlds } = useTlds()
|
|
23
|
+
|
|
24
|
+
// Bad: creates new objects on every render, reparenting the entire list on every keystroke
|
|
25
|
+
const domains = tlds.map((tld) => ({
|
|
26
|
+
domain: `${keyword}.${tld.name}`,
|
|
27
|
+
tld: tld.name,
|
|
28
|
+
price: tld.price,
|
|
29
|
+
}))
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<TextInput value={keyword} onChangeText={setKeyword} />
|
|
34
|
+
<LegendList
|
|
35
|
+
data={domains}
|
|
36
|
+
renderItem={({ item }) => <DomainItem item={item} keyword={keyword} />}
|
|
37
|
+
/>
|
|
38
|
+
</>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Correct (stable references, transform inside items):**
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
const renderItem = ({ item }) => <DomainItem tld={item} />
|
|
47
|
+
|
|
48
|
+
function DomainSearch() {
|
|
49
|
+
const { data: tlds } = useTlds()
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<LegendList
|
|
53
|
+
// good: as long as the data is stable, LegendList will not re-render the entire list
|
|
54
|
+
data={tlds}
|
|
55
|
+
renderItem={renderItem}
|
|
56
|
+
/>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function DomainItem({ tld }: { tld: Tld }) {
|
|
61
|
+
// good: transform within items, and don't pass the dynamic data as a prop
|
|
62
|
+
// good: use a selector function from zustand to receive a stable string back
|
|
63
|
+
const domain = useKeywordZustandState((s) => s.keyword + '.' + tld.name)
|
|
64
|
+
return <Text>{domain}</Text>
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Updating parent array reference:**
|
|
69
|
+
|
|
70
|
+
Creating a new array instance can be okay, as long as its inner object
|
|
71
|
+
references are stable. For instance, if you sort a list of objects:
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
// good: creates a new array instance without mutating the inner objects
|
|
75
|
+
// good: parent array reference is unaffected by typing and updating "keyword"
|
|
76
|
+
const sortedTlds = tlds.toSorted((a, b) => a.name.localeCompare(b.name))
|
|
77
|
+
|
|
78
|
+
return <LegendList data={sortedTlds} renderItem={renderItem} />
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Even though this creates a new array instance `sortedTlds`, the inner object
|
|
82
|
+
references are stable.
|
|
83
|
+
|
|
84
|
+
**With zustand for dynamic data (avoids parent re-renders):**
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
const useSearchStore = create<{ keyword: string }>(() => ({ keyword: '' }))
|
|
88
|
+
|
|
89
|
+
function DomainSearch() {
|
|
90
|
+
const { data: tlds } = useTlds()
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<>
|
|
94
|
+
<SearchInput />
|
|
95
|
+
<LegendList
|
|
96
|
+
data={tlds}
|
|
97
|
+
// if you aren't using React Compiler, wrap renderItem with useCallback
|
|
98
|
+
renderItem={({ item }) => <DomainItem tld={item} />}
|
|
99
|
+
/>
|
|
100
|
+
</>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function DomainItem({ tld }: { tld: Tld }) {
|
|
105
|
+
// Select only what you need—component only re-renders when keyword changes
|
|
106
|
+
const keyword = useSearchStore((s) => s.keyword)
|
|
107
|
+
const domain = `${keyword}.${tld.name}`
|
|
108
|
+
return <Text>{domain}</Text>
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Virtualization can now skip items that haven't changed when typing. Only visible
|
|
113
|
+
items (~20) re-render on keystroke, rather than the parent.
|
|
114
|
+
|
|
115
|
+
**Deriving state within list items based on parent data (avoids parent
|
|
116
|
+
re-renders):**
|
|
117
|
+
|
|
118
|
+
For components where the data is conditional based on the parent state, this
|
|
119
|
+
pattern is even more important. For example, if you are checking if an item is
|
|
120
|
+
favorited, toggling favorites only re-renders one component if the item itself
|
|
121
|
+
is in charge of accessing the state rather than the parent:
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
function DomainItemFavoriteButton({ tld }: { tld: Tld }) {
|
|
125
|
+
const isFavorited = useFavoritesStore((s) => s.favorites.has(tld.id))
|
|
126
|
+
return <TldFavoriteButton isFavorited={isFavorited} />
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Note: if you're using the React Compiler, you can read React Context values
|
|
131
|
+
directly within list items. Although this is slightly slower than using a
|
|
132
|
+
Zustand selector in most cases, the effect may be negligible.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Compressed Images in Lists
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: faster load times, less memory
|
|
5
|
+
tags: lists, images, performance, optimization
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Compressed Images in Lists
|
|
9
|
+
|
|
10
|
+
Always load compressed, appropriately-sized images in lists. Full-resolution
|
|
11
|
+
images consume excessive memory and cause scroll jank. Request thumbnails from
|
|
12
|
+
your server or use an image CDN with resize parameters.
|
|
13
|
+
|
|
14
|
+
**Incorrect (full-resolution images):**
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
function ProductItem({ product }: { product: Product }) {
|
|
18
|
+
return (
|
|
19
|
+
<View>
|
|
20
|
+
{/* 4000x3000 image loaded for a 100x100 thumbnail */}
|
|
21
|
+
<Image
|
|
22
|
+
source={{ uri: product.imageUrl }}
|
|
23
|
+
style={{ width: 100, height: 100 }}
|
|
24
|
+
/>
|
|
25
|
+
<Text>{product.name}</Text>
|
|
26
|
+
</View>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Correct (request appropriately-sized image):**
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
function ProductItem({ product }: { product: Product }) {
|
|
35
|
+
// Request a 200x200 image (2x for retina)
|
|
36
|
+
const thumbnailUrl = `${product.imageUrl}?w=200&h=200&fit=cover`
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<View>
|
|
40
|
+
<Image
|
|
41
|
+
source={{ uri: thumbnailUrl }}
|
|
42
|
+
style={{ width: 100, height: 100 }}
|
|
43
|
+
contentFit='cover'
|
|
44
|
+
/>
|
|
45
|
+
<Text>{product.name}</Text>
|
|
46
|
+
</View>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Use an optimized image component with built-in caching and placeholder support,
|
|
52
|
+
such as `expo-image` or `SolitoImage` (which uses `expo-image` under the hood).
|
|
53
|
+
Request images at 2x the display size for retina screens.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Avoid Inline Objects in renderItem
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents unnecessary re-renders of memoized list items
|
|
5
|
+
tags: lists, performance, flatlist, virtualization, memo
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Avoid Inline Objects in renderItem
|
|
9
|
+
|
|
10
|
+
Don't create new objects inside `renderItem` to pass as props. Inline objects
|
|
11
|
+
create new references on every render, breaking memoization. Pass primitive
|
|
12
|
+
values directly from `item` instead.
|
|
13
|
+
|
|
14
|
+
**Incorrect (inline object breaks memoization):**
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
function UserList({ users }: { users: User[] }) {
|
|
18
|
+
return (
|
|
19
|
+
<LegendList
|
|
20
|
+
data={users}
|
|
21
|
+
renderItem={({ item }) => (
|
|
22
|
+
<UserRow
|
|
23
|
+
// Bad: new object on every render
|
|
24
|
+
user={{ id: item.id, name: item.name, avatar: item.avatar }}
|
|
25
|
+
/>
|
|
26
|
+
)}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Incorrect (inline style object):**
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
renderItem={({ item }) => (
|
|
36
|
+
<UserRow
|
|
37
|
+
name={item.name}
|
|
38
|
+
// Bad: new style object on every render
|
|
39
|
+
style={{ backgroundColor: item.isActive ? 'green' : 'gray' }}
|
|
40
|
+
/>
|
|
41
|
+
)}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Correct (pass item directly or primitives):**
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
function UserList({ users }: { users: User[] }) {
|
|
48
|
+
return (
|
|
49
|
+
<LegendList
|
|
50
|
+
data={users}
|
|
51
|
+
renderItem={({ item }) => (
|
|
52
|
+
// Good: pass the item directly
|
|
53
|
+
<UserRow user={item} />
|
|
54
|
+
)}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Correct (pass primitives, derive inside child):**
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
renderItem={({ item }) => (
|
|
64
|
+
<UserRow
|
|
65
|
+
id={item.id}
|
|
66
|
+
name={item.name}
|
|
67
|
+
isActive={item.isActive}
|
|
68
|
+
/>
|
|
69
|
+
)}
|
|
70
|
+
|
|
71
|
+
const UserRow = memo(function UserRow({ id, name, isActive }: Props) {
|
|
72
|
+
// Good: derive style inside memoized component
|
|
73
|
+
const backgroundColor = isActive ? 'green' : 'gray'
|
|
74
|
+
return <View style={[styles.row, { backgroundColor }]}>{/* ... */}</View>
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Correct (hoist static styles in module scope):**
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
const activeStyle = { backgroundColor: 'green' }
|
|
82
|
+
const inactiveStyle = { backgroundColor: 'gray' }
|
|
83
|
+
|
|
84
|
+
renderItem={({ item }) => (
|
|
85
|
+
<UserRow
|
|
86
|
+
name={item.name}
|
|
87
|
+
// Good: stable references
|
|
88
|
+
style={item.isActive ? activeStyle : inactiveStyle}
|
|
89
|
+
/>
|
|
90
|
+
)}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Passing primitives or stable references allows `memo()` to skip re-renders when
|
|
94
|
+
the actual values haven't changed.
|
|
95
|
+
|
|
96
|
+
**Note:** If you have the React Compiler enabled, it handles memoization
|
|
97
|
+
automatically and these manual optimizations become less critical.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Keep List Items Lightweight
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: reduces render time for visible items during scroll
|
|
5
|
+
tags: lists, performance, virtualization, hooks
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Keep List Items Lightweight
|
|
9
|
+
|
|
10
|
+
List items should be as inexpensive as possible to render. Minimize hooks, avoid
|
|
11
|
+
queries, and limit React Context access. Virtualized lists render many items
|
|
12
|
+
during scroll—expensive items cause jank.
|
|
13
|
+
|
|
14
|
+
**Incorrect (heavy list item):**
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
function ProductRow({ id }: { id: string }) {
|
|
18
|
+
// Bad: query inside list item
|
|
19
|
+
const { data: product } = useQuery(['product', id], () => fetchProduct(id))
|
|
20
|
+
// Bad: multiple context accesses
|
|
21
|
+
const theme = useContext(ThemeContext)
|
|
22
|
+
const user = useContext(UserContext)
|
|
23
|
+
const cart = useContext(CartContext)
|
|
24
|
+
// Bad: expensive computation
|
|
25
|
+
const recommendations = useMemo(
|
|
26
|
+
() => computeRecommendations(product),
|
|
27
|
+
[product]
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
return <View>{/* ... */}</View>
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Correct (lightweight list item):**
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
function ProductRow({ name, price, imageUrl }: Props) {
|
|
38
|
+
// Good: receives only primitives, minimal hooks
|
|
39
|
+
return (
|
|
40
|
+
<View>
|
|
41
|
+
<Image source={{ uri: imageUrl }} />
|
|
42
|
+
<Text>{name}</Text>
|
|
43
|
+
<Text>{price}</Text>
|
|
44
|
+
</View>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Move data fetching to parent:**
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
// Parent fetches all data once
|
|
53
|
+
function ProductList() {
|
|
54
|
+
const { data: products } = useQuery(['products'], fetchProducts)
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<LegendList
|
|
58
|
+
data={products}
|
|
59
|
+
renderItem={({ item }) => (
|
|
60
|
+
<ProductRow name={item.name} price={item.price} imageUrl={item.image} />
|
|
61
|
+
)}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**For shared values, use Zustand selectors instead of Context:**
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
// Incorrect: Context causes re-render when any cart value changes
|
|
71
|
+
function ProductRow({ id, name }: Props) {
|
|
72
|
+
const { items } = useContext(CartContext)
|
|
73
|
+
const inCart = items.includes(id)
|
|
74
|
+
// ...
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Correct: Zustand selector only re-renders when this specific value changes
|
|
78
|
+
function ProductRow({ id, name }: Props) {
|
|
79
|
+
// use Set.has (created once at the root) instead of Array.includes()
|
|
80
|
+
const inCart = useCartStore((s) => s.items.has(id))
|
|
81
|
+
// ...
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Guidelines for list items:**
|
|
86
|
+
|
|
87
|
+
- No queries or data fetching
|
|
88
|
+
- No expensive computations (move to parent or memoize at parent level)
|
|
89
|
+
- Prefer Zustand selectors over React Context
|
|
90
|
+
- Minimize useState/useEffect hooks
|
|
91
|
+
- Pass pre-computed values as props
|
|
92
|
+
|
|
93
|
+
The goal: list items should be simple rendering functions that take props and
|
|
94
|
+
return JSX.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Pass Primitives to List Items for Memoization
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: enables effective memo() comparison
|
|
5
|
+
tags: lists, performance, memo, primitives
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Pass Primitives to List Items for Memoization
|
|
9
|
+
|
|
10
|
+
When possible, pass only primitive values (strings, numbers, booleans) as props
|
|
11
|
+
to list item components. Primitives enable shallow comparison in `memo()` to
|
|
12
|
+
work correctly, skipping re-renders when values haven't changed.
|
|
13
|
+
|
|
14
|
+
**Incorrect (object prop requires deep comparison):**
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
type User = { id: string; name: string; email: string; avatar: string }
|
|
18
|
+
|
|
19
|
+
const UserRow = memo(function UserRow({ user }: { user: User }) {
|
|
20
|
+
// memo() compares user by reference, not value
|
|
21
|
+
// If parent creates new user object, this re-renders even if data is same
|
|
22
|
+
return <Text>{user.name}</Text>
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
renderItem={({ item }) => <UserRow user={item} />}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This can still be optimized, but it is harder to memoize properly.
|
|
29
|
+
|
|
30
|
+
**Correct (primitive props enable shallow comparison):**
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
const UserRow = memo(function UserRow({
|
|
34
|
+
id,
|
|
35
|
+
name,
|
|
36
|
+
email,
|
|
37
|
+
}: {
|
|
38
|
+
id: string
|
|
39
|
+
name: string
|
|
40
|
+
email: string
|
|
41
|
+
}) {
|
|
42
|
+
// memo() compares each primitive directly
|
|
43
|
+
// Re-renders only if id, name, or email actually changed
|
|
44
|
+
return <Text>{name}</Text>
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
renderItem={({ item }) => (
|
|
48
|
+
<UserRow id={item.id} name={item.name} email={item.email} />
|
|
49
|
+
)}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Pass only what you need:**
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
// Incorrect: passing entire item when you only need name
|
|
56
|
+
<UserRow user={item} />
|
|
57
|
+
|
|
58
|
+
// Correct: pass only the fields the component uses
|
|
59
|
+
<UserRow name={item.name} avatarUrl={item.avatar} />
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**For callbacks, hoist or use item ID:**
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
// Incorrect: inline function creates new reference
|
|
66
|
+
<UserRow name={item.name} onPress={() => handlePress(item.id)} />
|
|
67
|
+
|
|
68
|
+
// Correct: pass ID, handle in child
|
|
69
|
+
<UserRow id={item.id} name={item.name} />
|
|
70
|
+
|
|
71
|
+
const UserRow = memo(function UserRow({ id, name }: Props) {
|
|
72
|
+
const handlePress = useCallback(() => {
|
|
73
|
+
// use id here
|
|
74
|
+
}, [id])
|
|
75
|
+
return <Pressable onPress={handlePress}><Text>{name}</Text></Pressable>
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Primitive props make memoization predictable and effective.
|
|
80
|
+
|
|
81
|
+
**Note:** If you have the React Compiler enabled, you do not need to use
|
|
82
|
+
`memo()` or `useCallback()`, but the object references still apply.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Item Types for Heterogeneous Lists
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: efficient recycling, less layout thrashing
|
|
5
|
+
tags: list, performance, recycling, heterogeneous, LegendList
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Item Types for Heterogeneous Lists
|
|
9
|
+
|
|
10
|
+
When a list has different item layouts (messages, images, headers, etc.), use a
|
|
11
|
+
`type` field on each item and provide `getItemType` to the list. This puts items
|
|
12
|
+
into separate recycling pools so a message component never gets recycled into an
|
|
13
|
+
image component.
|
|
14
|
+
|
|
15
|
+
**Incorrect (single component with conditionals):**
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
type Item = { id: string; text?: string; imageUrl?: string; isHeader?: boolean }
|
|
19
|
+
|
|
20
|
+
function ListItem({ item }: { item: Item }) {
|
|
21
|
+
if (item.isHeader) {
|
|
22
|
+
return <HeaderItem title={item.text} />
|
|
23
|
+
}
|
|
24
|
+
if (item.imageUrl) {
|
|
25
|
+
return <ImageItem url={item.imageUrl} />
|
|
26
|
+
}
|
|
27
|
+
return <MessageItem text={item.text} />
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function Feed({ items }: { items: Item[] }) {
|
|
31
|
+
return (
|
|
32
|
+
<LegendList
|
|
33
|
+
data={items}
|
|
34
|
+
renderItem={({ item }) => <ListItem item={item} />}
|
|
35
|
+
recycleItems
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Correct (typed items with separate components):**
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
type HeaderItem = { id: string; type: 'header'; title: string }
|
|
45
|
+
type MessageItem = { id: string; type: 'message'; text: string }
|
|
46
|
+
type ImageItem = { id: string; type: 'image'; url: string }
|
|
47
|
+
type FeedItem = HeaderItem | MessageItem | ImageItem
|
|
48
|
+
|
|
49
|
+
function Feed({ items }: { items: FeedItem[] }) {
|
|
50
|
+
return (
|
|
51
|
+
<LegendList
|
|
52
|
+
data={items}
|
|
53
|
+
keyExtractor={(item) => item.id}
|
|
54
|
+
getItemType={(item) => item.type}
|
|
55
|
+
renderItem={({ item }) => {
|
|
56
|
+
switch (item.type) {
|
|
57
|
+
case 'header':
|
|
58
|
+
return <SectionHeader title={item.title} />
|
|
59
|
+
case 'message':
|
|
60
|
+
return <MessageRow text={item.text} />
|
|
61
|
+
case 'image':
|
|
62
|
+
return <ImageRow url={item.url} />
|
|
63
|
+
}
|
|
64
|
+
}}
|
|
65
|
+
recycleItems
|
|
66
|
+
/>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Why this matters:**
|
|
72
|
+
|
|
73
|
+
- **Recycling efficiency**: Items with the same type share a recycling pool
|
|
74
|
+
- **No layout thrashing**: A header never recycles into an image cell
|
|
75
|
+
- **Type safety**: TypeScript can narrow the item type in each branch
|
|
76
|
+
- **Better size estimation**: Use `getEstimatedItemSize` with `itemType` for
|
|
77
|
+
accurate estimates per type
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
<LegendList
|
|
81
|
+
data={items}
|
|
82
|
+
keyExtractor={(item) => item.id}
|
|
83
|
+
getItemType={(item) => item.type}
|
|
84
|
+
getEstimatedItemSize={(index, item, itemType) => {
|
|
85
|
+
switch (itemType) {
|
|
86
|
+
case 'header':
|
|
87
|
+
return 48
|
|
88
|
+
case 'message':
|
|
89
|
+
return 72
|
|
90
|
+
case 'image':
|
|
91
|
+
return 300
|
|
92
|
+
default:
|
|
93
|
+
return 72
|
|
94
|
+
}
|
|
95
|
+
}}
|
|
96
|
+
renderItem={({ item }) => {
|
|
97
|
+
/* ... */
|
|
98
|
+
}}
|
|
99
|
+
recycleItems
|
|
100
|
+
/>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Reference:
|
|
104
|
+
[LegendList getItemType](https://legendapp.com/open-source/list/api/props/#getitemtype-v2)
|