@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,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use a List Virtualizer for Any List
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: reduced memory, faster mounts
|
|
5
|
+
tags: lists, performance, virtualization, scrollview
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use a List Virtualizer for Any List
|
|
9
|
+
|
|
10
|
+
Use a list virtualizer like LegendList or FlashList instead of ScrollView with
|
|
11
|
+
mapped children—even for short lists. Virtualizers only render visible items,
|
|
12
|
+
reducing memory usage and mount time. ScrollView renders all children upfront,
|
|
13
|
+
which gets expensive quickly.
|
|
14
|
+
|
|
15
|
+
**Incorrect (ScrollView renders all items at once):**
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
function Feed({ items }: { items: Item[] }) {
|
|
19
|
+
return (
|
|
20
|
+
<ScrollView>
|
|
21
|
+
{items.map((item) => (
|
|
22
|
+
<ItemCard key={item.id} item={item} />
|
|
23
|
+
))}
|
|
24
|
+
</ScrollView>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
// 50 items = 50 components mounted, even if only 10 visible
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Correct (virtualizer renders only visible items):**
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { LegendList } from '@legendapp/list'
|
|
34
|
+
|
|
35
|
+
function Feed({ items }: { items: Item[] }) {
|
|
36
|
+
return (
|
|
37
|
+
<LegendList
|
|
38
|
+
data={items}
|
|
39
|
+
// if you aren't using React Compiler, wrap these with useCallback
|
|
40
|
+
renderItem={({ item }) => <ItemCard item={item} />}
|
|
41
|
+
keyExtractor={(item) => item.id}
|
|
42
|
+
estimatedItemSize={80}
|
|
43
|
+
/>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
// Only ~10-15 visible items mounted at a time
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Alternative (FlashList):**
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
import { FlashList } from '@shopify/flash-list'
|
|
53
|
+
|
|
54
|
+
function Feed({ items }: { items: Item[] }) {
|
|
55
|
+
return (
|
|
56
|
+
<FlashList
|
|
57
|
+
data={items}
|
|
58
|
+
// if you aren't using React Compiler, wrap these with useCallback
|
|
59
|
+
renderItem={({ item }) => <ItemCard item={item} />}
|
|
60
|
+
keyExtractor={(item) => item.id}
|
|
61
|
+
/>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Benefits apply to any screen with scrollable content—profiles, settings, feeds,
|
|
67
|
+
search results. Default to virtualization.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Install Native Dependencies in App Directory
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: required for autolinking to work
|
|
5
|
+
tags: monorepo, native, autolinking, installation
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Install Native Dependencies in App Directory
|
|
9
|
+
|
|
10
|
+
In a monorepo, packages with native code must be installed in the native app's
|
|
11
|
+
directory directly. Autolinking only scans the app's `node_modules`—it won't
|
|
12
|
+
find native dependencies installed in other packages.
|
|
13
|
+
|
|
14
|
+
**Incorrect (native dep in shared package only):**
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
packages/
|
|
18
|
+
ui/
|
|
19
|
+
package.json # has react-native-reanimated
|
|
20
|
+
app/
|
|
21
|
+
package.json # missing react-native-reanimated
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Autolinking fails—native code not linked.
|
|
25
|
+
|
|
26
|
+
**Correct (native dep in app directory):**
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
packages/
|
|
30
|
+
ui/
|
|
31
|
+
package.json # has react-native-reanimated
|
|
32
|
+
app/
|
|
33
|
+
package.json # also has react-native-reanimated
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
// packages/app/package.json
|
|
38
|
+
{
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"react-native-reanimated": "3.16.1"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Even if the shared package uses the native dependency, the app must also list it
|
|
46
|
+
for autolinking to detect and link the native code.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Single Dependency Versions Across Monorepo
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: avoids duplicate bundles, version conflicts
|
|
5
|
+
tags: monorepo, dependencies, installation
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Single Dependency Versions Across Monorepo
|
|
9
|
+
|
|
10
|
+
Use a single version of each dependency across all packages in your monorepo.
|
|
11
|
+
Prefer exact versions over ranges. Multiple versions cause duplicate code in
|
|
12
|
+
bundles, runtime conflicts, and inconsistent behavior across packages.
|
|
13
|
+
|
|
14
|
+
Use a tool like syncpack to enforce this. As a last resort, use yarn resolutions
|
|
15
|
+
or npm overrides.
|
|
16
|
+
|
|
17
|
+
**Incorrect (version ranges, multiple versions):**
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
// packages/app/package.json
|
|
21
|
+
{
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"react-native-reanimated": "^3.0.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// packages/ui/package.json
|
|
28
|
+
{
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"react-native-reanimated": "^3.5.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Correct (exact versions, single source of truth):**
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
// package.json (root)
|
|
39
|
+
{
|
|
40
|
+
"pnpm": {
|
|
41
|
+
"overrides": {
|
|
42
|
+
"react-native-reanimated": "3.16.1"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// packages/app/package.json
|
|
48
|
+
{
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"react-native-reanimated": "3.16.1"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// packages/ui/package.json
|
|
55
|
+
{
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"react-native-reanimated": "3.16.1"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Use your package manager's override/resolution feature to enforce versions at
|
|
63
|
+
the root. When adding dependencies, specify exact versions without `^` or `~`.
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Native Navigators for Navigation
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: native performance, platform-appropriate UI
|
|
5
|
+
tags: navigation, react-navigation, expo-router, native-stack, tabs
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Native Navigators for Navigation
|
|
9
|
+
|
|
10
|
+
Always use native navigators instead of JS-based ones. Native navigators use
|
|
11
|
+
platform APIs (UINavigationController on iOS, Fragment on Android) for better
|
|
12
|
+
performance and native behavior.
|
|
13
|
+
|
|
14
|
+
**For stacks:** Use `@react-navigation/native-stack` or expo-router's default
|
|
15
|
+
stack (which uses native-stack). Avoid `@react-navigation/stack`.
|
|
16
|
+
|
|
17
|
+
**For tabs:** Use `react-native-bottom-tabs` (native) or expo-router's native
|
|
18
|
+
tabs. Avoid `@react-navigation/bottom-tabs` when native feel matters.
|
|
19
|
+
|
|
20
|
+
### Stack Navigation
|
|
21
|
+
|
|
22
|
+
**Incorrect (JS stack navigator):**
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { createStackNavigator } from '@react-navigation/stack'
|
|
26
|
+
|
|
27
|
+
const Stack = createStackNavigator()
|
|
28
|
+
|
|
29
|
+
function App() {
|
|
30
|
+
return (
|
|
31
|
+
<Stack.Navigator>
|
|
32
|
+
<Stack.Screen name='Home' component={HomeScreen} />
|
|
33
|
+
<Stack.Screen name='Details' component={DetailsScreen} />
|
|
34
|
+
</Stack.Navigator>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Correct (native stack with react-navigation):**
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
|
43
|
+
|
|
44
|
+
const Stack = createNativeStackNavigator()
|
|
45
|
+
|
|
46
|
+
function App() {
|
|
47
|
+
return (
|
|
48
|
+
<Stack.Navigator>
|
|
49
|
+
<Stack.Screen name='Home' component={HomeScreen} />
|
|
50
|
+
<Stack.Screen name='Details' component={DetailsScreen} />
|
|
51
|
+
</Stack.Navigator>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Correct (expo-router uses native stack by default):**
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// app/_layout.tsx
|
|
60
|
+
import { Stack } from 'expo-router'
|
|
61
|
+
|
|
62
|
+
export default function Layout() {
|
|
63
|
+
return <Stack />
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Tab Navigation
|
|
68
|
+
|
|
69
|
+
**Incorrect (JS bottom tabs):**
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
|
|
73
|
+
|
|
74
|
+
const Tab = createBottomTabNavigator()
|
|
75
|
+
|
|
76
|
+
function App() {
|
|
77
|
+
return (
|
|
78
|
+
<Tab.Navigator>
|
|
79
|
+
<Tab.Screen name='Home' component={HomeScreen} />
|
|
80
|
+
<Tab.Screen name='Settings' component={SettingsScreen} />
|
|
81
|
+
</Tab.Navigator>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Correct (native bottom tabs with react-navigation):**
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { createNativeBottomTabNavigator } from '@bottom-tabs/react-navigation'
|
|
90
|
+
|
|
91
|
+
const Tab = createNativeBottomTabNavigator()
|
|
92
|
+
|
|
93
|
+
function App() {
|
|
94
|
+
return (
|
|
95
|
+
<Tab.Navigator>
|
|
96
|
+
<Tab.Screen
|
|
97
|
+
name='Home'
|
|
98
|
+
component={HomeScreen}
|
|
99
|
+
options={{
|
|
100
|
+
tabBarIcon: () => ({ sfSymbol: 'house' }),
|
|
101
|
+
}}
|
|
102
|
+
/>
|
|
103
|
+
<Tab.Screen
|
|
104
|
+
name='Settings'
|
|
105
|
+
component={SettingsScreen}
|
|
106
|
+
options={{
|
|
107
|
+
tabBarIcon: () => ({ sfSymbol: 'gear' }),
|
|
108
|
+
}}
|
|
109
|
+
/>
|
|
110
|
+
</Tab.Navigator>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Correct (expo-router native tabs):**
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
// app/(tabs)/_layout.tsx
|
|
119
|
+
import { NativeTabs } from 'expo-router/unstable-native-tabs'
|
|
120
|
+
|
|
121
|
+
export default function TabLayout() {
|
|
122
|
+
return (
|
|
123
|
+
<NativeTabs>
|
|
124
|
+
<NativeTabs.Trigger name='index'>
|
|
125
|
+
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
|
|
126
|
+
<NativeTabs.Trigger.Icon sf='house.fill' md='home' />
|
|
127
|
+
</NativeTabs.Trigger>
|
|
128
|
+
<NativeTabs.Trigger name='settings'>
|
|
129
|
+
<NativeTabs.Trigger.Label>Settings</NativeTabs.Trigger.Label>
|
|
130
|
+
<NativeTabs.Trigger.Icon sf='gear' md='settings' />
|
|
131
|
+
</NativeTabs.Trigger>
|
|
132
|
+
</NativeTabs>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
On iOS, native tabs automatically enable `contentInsetAdjustmentBehavior` on the
|
|
138
|
+
first `ScrollView` at the root of each tab screen, so content scrolls correctly
|
|
139
|
+
behind the translucent tab bar. If you need to disable this, use
|
|
140
|
+
`disableAutomaticContentInsets` on the trigger.
|
|
141
|
+
|
|
142
|
+
### Prefer Native Header Options Over Custom Components
|
|
143
|
+
|
|
144
|
+
**Incorrect (custom header component):**
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
<Stack.Screen
|
|
148
|
+
name='Profile'
|
|
149
|
+
component={ProfileScreen}
|
|
150
|
+
options={{
|
|
151
|
+
header: () => <CustomHeader title='Profile' />,
|
|
152
|
+
}}
|
|
153
|
+
/>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Correct (native header options):**
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
<Stack.Screen
|
|
160
|
+
name='Profile'
|
|
161
|
+
component={ProfileScreen}
|
|
162
|
+
options={{
|
|
163
|
+
title: 'Profile',
|
|
164
|
+
headerLargeTitleEnabled: true,
|
|
165
|
+
headerSearchBarOptions: {
|
|
166
|
+
placeholder: 'Search',
|
|
167
|
+
},
|
|
168
|
+
}}
|
|
169
|
+
/>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Native headers support iOS large titles, search bars, blur effects, and proper
|
|
173
|
+
safe area handling automatically.
|
|
174
|
+
|
|
175
|
+
### Why Native Navigators
|
|
176
|
+
|
|
177
|
+
- **Performance**: Native transitions and gestures run on the UI thread
|
|
178
|
+
- **Platform behavior**: Automatic iOS large titles, Android material design
|
|
179
|
+
- **System integration**: Scroll-to-top on tab tap, PiP avoidance, proper safe
|
|
180
|
+
areas
|
|
181
|
+
- **Accessibility**: Platform accessibility features work automatically
|
|
182
|
+
|
|
183
|
+
Reference:
|
|
184
|
+
|
|
185
|
+
- [React Navigation Native Stack](https://reactnavigation.org/docs/native-stack-navigator)
|
|
186
|
+
- [React Native Bottom Tabs with React Navigation](https://oss.callstack.com/react-native-bottom-tabs/docs/guides/usage-with-react-navigation)
|
|
187
|
+
- [React Native Bottom Tabs with Expo Router](https://oss.callstack.com/react-native-bottom-tabs/docs/guides/usage-with-expo-router)
|
|
188
|
+
- [Expo Router Native Tabs](https://docs.expo.dev/router/advanced/native-tabs)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Destructure Functions Early in Render (React Compiler)
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: stable references, fewer re-renders
|
|
5
|
+
tags: rerender, hooks, performance, react-compiler
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Destructure Functions Early in Render
|
|
9
|
+
|
|
10
|
+
This rule is only applicable if you are using the React Compiler.
|
|
11
|
+
|
|
12
|
+
Destructure functions from hooks at the top of render scope. Never dot into
|
|
13
|
+
objects to call functions. Destructured functions are stable references; dotting
|
|
14
|
+
creates new references and breaks memoization.
|
|
15
|
+
|
|
16
|
+
**Incorrect (dotting into object):**
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import { useRouter } from 'expo-router'
|
|
20
|
+
|
|
21
|
+
function SaveButton(props) {
|
|
22
|
+
const router = useRouter()
|
|
23
|
+
|
|
24
|
+
// bad: react-compiler will key the cache on "props" and "router", which are objects that change each render
|
|
25
|
+
const handlePress = () => {
|
|
26
|
+
props.onSave()
|
|
27
|
+
router.push('/success') // unstable reference
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return <Button onPress={handlePress}>Save</Button>
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Correct (destructure early):**
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { useRouter } from 'expo-router'
|
|
38
|
+
|
|
39
|
+
function SaveButton({ onSave }) {
|
|
40
|
+
const { push } = useRouter()
|
|
41
|
+
|
|
42
|
+
// good: react-compiler will key on push and onSave
|
|
43
|
+
const handlePress = () => {
|
|
44
|
+
onSave()
|
|
45
|
+
push('/success') // stable reference
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return <Button onPress={handlePress}>Save</Button>
|
|
49
|
+
}
|
|
50
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use .get() and .set() for Reanimated Shared Values (not .value)
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: required for React Compiler compatibility
|
|
5
|
+
tags: reanimated, react-compiler, shared-values
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use .get() and .set() for Shared Values with React Compiler
|
|
9
|
+
|
|
10
|
+
With React Compiler enabled, use `.get()` and `.set()` instead of reading or
|
|
11
|
+
writing `.value` directly on Reanimated shared values. The compiler can't track
|
|
12
|
+
property access—explicit methods ensure correct behavior.
|
|
13
|
+
|
|
14
|
+
**Incorrect (breaks with React Compiler):**
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { useSharedValue } from 'react-native-reanimated'
|
|
18
|
+
|
|
19
|
+
function Counter() {
|
|
20
|
+
const count = useSharedValue(0)
|
|
21
|
+
|
|
22
|
+
const increment = () => {
|
|
23
|
+
count.value = count.value + 1 // opts out of react compiler
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return <Button onPress={increment} title={`Count: ${count.value}`} />
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Correct (React Compiler compatible):**
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { useSharedValue } from 'react-native-reanimated'
|
|
34
|
+
|
|
35
|
+
function Counter() {
|
|
36
|
+
const count = useSharedValue(0)
|
|
37
|
+
|
|
38
|
+
const increment = () => {
|
|
39
|
+
count.set(count.get() + 1)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return <Button onPress={increment} title={`Count: ${count.get()}`} />
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
See the
|
|
47
|
+
[Reanimated docs](https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/#react-compiler-support)
|
|
48
|
+
for more.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: useState Dispatch updaters for State That Depends on Current Value
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: avoids stale closures, prevents unnecessary re-renders
|
|
5
|
+
tags: state, hooks, useState, callbacks
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Dispatch Updaters for State That Depends on Current Value
|
|
9
|
+
|
|
10
|
+
When the next state depends on the current state, use a dispatch updater
|
|
11
|
+
(`setState(prev => ...)`) instead of reading the state variable directly in a
|
|
12
|
+
callback. This avoids stale closures and ensures you're comparing against the
|
|
13
|
+
latest value.
|
|
14
|
+
|
|
15
|
+
**Incorrect (reads state directly):**
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
const [size, setSize] = useState<Size | undefined>(undefined)
|
|
19
|
+
|
|
20
|
+
const onLayout = (e: LayoutChangeEvent) => {
|
|
21
|
+
const { width, height } = e.nativeEvent.layout
|
|
22
|
+
// size may be stale in this closure
|
|
23
|
+
if (size?.width !== width || size?.height !== height) {
|
|
24
|
+
setSize({ width, height })
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Correct (dispatch updater):**
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
const [size, setSize] = useState<Size | undefined>(undefined)
|
|
33
|
+
|
|
34
|
+
const onLayout = (e: LayoutChangeEvent) => {
|
|
35
|
+
const { width, height } = e.nativeEvent.layout
|
|
36
|
+
setSize((prev) => {
|
|
37
|
+
if (prev?.width === width && prev?.height === height) return prev
|
|
38
|
+
return { width, height }
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Returning the previous value from the updater skips the re-render.
|
|
44
|
+
|
|
45
|
+
For primitive states, you don't need to compare values before firing a
|
|
46
|
+
re-render.
|
|
47
|
+
|
|
48
|
+
**Incorrect (unnecessary comparison for primitive state):**
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
const [size, setSize] = useState<Size | undefined>(undefined)
|
|
52
|
+
|
|
53
|
+
const onLayout = (e: LayoutChangeEvent) => {
|
|
54
|
+
const { width, height } = e.nativeEvent.layout
|
|
55
|
+
setSize((prev) => (prev === width ? prev : width))
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Correct (sets primitive state directly):**
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
const [size, setSize] = useState<Size | undefined>(undefined)
|
|
63
|
+
|
|
64
|
+
const onLayout = (e: LayoutChangeEvent) => {
|
|
65
|
+
const { width, height } = e.nativeEvent.layout
|
|
66
|
+
setSize(width)
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
However, if the next state depends on the current state, you should still use a
|
|
71
|
+
dispatch updater.
|
|
72
|
+
|
|
73
|
+
**Incorrect (reads state directly from the callback):**
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
const [count, setCount] = useState(0)
|
|
77
|
+
|
|
78
|
+
const onTap = () => {
|
|
79
|
+
setCount(count + 1)
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Correct (dispatch updater):**
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
const [count, setCount] = useState(0)
|
|
87
|
+
|
|
88
|
+
const onTap = () => {
|
|
89
|
+
setCount((prev) => prev + 1)
|
|
90
|
+
}
|
|
91
|
+
```
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use fallback state instead of initialState
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: reactive fallbacks without syncing
|
|
5
|
+
tags: state, hooks, derived-state, props, initialState
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use fallback state instead of initialState
|
|
9
|
+
|
|
10
|
+
Use `undefined` as initial state and nullish coalescing (`??`) to fall back to
|
|
11
|
+
parent or server values. State represents user intent only—`undefined` means
|
|
12
|
+
"user hasn't chosen yet." This enables reactive fallbacks that update when the
|
|
13
|
+
source changes, not just on initial render.
|
|
14
|
+
|
|
15
|
+
**Incorrect (syncs state, loses reactivity):**
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
type Props = { fallbackEnabled: boolean }
|
|
19
|
+
|
|
20
|
+
function Toggle({ fallbackEnabled }: Props) {
|
|
21
|
+
const [enabled, setEnabled] = useState(defaultEnabled)
|
|
22
|
+
// If fallbackEnabled changes, state is stale
|
|
23
|
+
// State mixes user intent with default value
|
|
24
|
+
|
|
25
|
+
return <Switch value={enabled} onValueChange={setEnabled} />
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Correct (state is user intent, reactive fallback):**
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
type Props = { fallbackEnabled: boolean }
|
|
33
|
+
|
|
34
|
+
function Toggle({ fallbackEnabled }: Props) {
|
|
35
|
+
const [_enabled, setEnabled] = useState<boolean | undefined>(undefined)
|
|
36
|
+
const enabled = _enabled ?? defaultEnabled
|
|
37
|
+
// undefined = user hasn't touched it, falls back to prop
|
|
38
|
+
// If defaultEnabled changes, component reflects it
|
|
39
|
+
// Once user interacts, their choice persists
|
|
40
|
+
|
|
41
|
+
return <Switch value={enabled} onValueChange={setEnabled} />
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**With server data:**
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
function ProfileForm({ data }: { data: User }) {
|
|
49
|
+
const [_theme, setTheme] = useState<string | undefined>(undefined)
|
|
50
|
+
const theme = _theme ?? data.theme
|
|
51
|
+
// Shows server value until user overrides
|
|
52
|
+
// Server refetch updates the fallback automatically
|
|
53
|
+
|
|
54
|
+
return <ThemePicker value={theme} onChange={setTheme} />
|
|
55
|
+
}
|
|
56
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Minimize State Variables and Derive Values
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: fewer re-renders, less state drift
|
|
5
|
+
tags: state, derived-state, hooks, optimization
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Minimize State Variables and Derive Values
|
|
9
|
+
|
|
10
|
+
Use the fewest state variables possible. If a value can be computed from existing state or props, derive it during render instead of storing it in state. Redundant state causes unnecessary re-renders and can drift out of sync.
|
|
11
|
+
|
|
12
|
+
**Incorrect (redundant state):**
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
function Cart({ items }: { items: Item[] }) {
|
|
16
|
+
const [total, setTotal] = useState(0)
|
|
17
|
+
const [itemCount, setItemCount] = useState(0)
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
setTotal(items.reduce((sum, item) => sum + item.price, 0))
|
|
21
|
+
setItemCount(items.length)
|
|
22
|
+
}, [items])
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<View>
|
|
26
|
+
<Text>{itemCount} items</Text>
|
|
27
|
+
<Text>Total: ${total}</Text>
|
|
28
|
+
</View>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Correct (derived values):**
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
function Cart({ items }: { items: Item[] }) {
|
|
37
|
+
const total = items.reduce((sum, item) => sum + item.price, 0)
|
|
38
|
+
const itemCount = items.length
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<View>
|
|
42
|
+
<Text>{itemCount} items</Text>
|
|
43
|
+
<Text>Total: ${total}</Text>
|
|
44
|
+
</View>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Another example:**
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
// Incorrect: storing both firstName, lastName, AND fullName
|
|
53
|
+
const [firstName, setFirstName] = useState('')
|
|
54
|
+
const [lastName, setLastName] = useState('')
|
|
55
|
+
const [fullName, setFullName] = useState('')
|
|
56
|
+
|
|
57
|
+
// Correct: derive fullName
|
|
58
|
+
const [firstName, setFirstName] = useState('')
|
|
59
|
+
const [lastName, setLastName] = useState('')
|
|
60
|
+
const fullName = `${firstName} ${lastName}`
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
State should be the minimal source of truth. Everything else is derived.
|
|
64
|
+
|
|
65
|
+
Reference: [Choosing the State Structure](https://react.dev/learn/choosing-the-state-structure)
|