Ctrl K
ComunidadGuías…

skills/expo-react-native/SKILL.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
---
name: expo-react-native
description: Build and change a React Native app on Expo (managed workflow) with Expo Router and NativeWind. Use for any project on the "Expo — React Native" stack — screens, navigation, styling, state, lists, platform APIs. It is NOT a web project; the rules below are what keep that true.
---

# React Native on Expo — how this project works

This project renders with **React Native**, not the DOM. It previews in a
browser through Expo web (react-native-web), but the code is native RN: the
same files run on iOS and Android. Writing it like a web app produces
something that renders nothing on device and usually nothing on web either.

## Primitives — MUST

- Build UI from `react-native`: `View`, `Text`, `Pressable`, `ScrollView`,
  `Image`, `TextInput`, `FlatList`, `SectionList`, `Modal`, `Switch`,
  `ActivityIndicator`, `KeyboardAvoidingView`.
- **NEVER** `div`, `span`, `p`, `button`, `a`, `img`, `ul`/`li`, `input`,
  `<form>`, or any HTML element. There is no `onClick` (`onPress`), no `href`
  (`<Link href>` from `expo-router`), no `className`-less DOM.
- **All text lives inside `<Text>`.** A bare string in a `<View>` throws
  "Text strings must be rendered within a <Text> component".
- No `window`, `document`, `localStorage`, `navigator`, `alert`, DOM events,
  `URL`/`fetch`-to-same-origin assumptions, or web-only npm packages
  (`react-router-dom`, `framer-motion`, `@mui/*`, anything importing `react-dom`
  directly). `fetch` itself is fine.

## Navigation — MUST

- Routes are files under `app/`. `app/index.tsx` is `/`, `app/settings.tsx`
  is `/settings`, `app/item/[id].tsx` is a dynamic route.
- Layouts are `app/_layout.tsx` (and nested `_layout.tsx`) exporting a `<Stack>`,
  `<Tabs>`, or `<Drawer>` from `expo-router`.
- Navigate with `<Link href="/settings">` or
  `import { router } from "expo-router"; router.push("/item/42")`. Read params
  with `useLocalSearchParams()`.
- Do not hand-roll a router, a history stack, or conditional screen rendering
  in one component.

## Styling — MUST

- Prefer NativeWind: `className="flex-1 items-center gap-4 bg-white"`. It is
  wired (`global.css`, `metro.config.js`, `tailwind.config.js`, the babel
  preset). `StyleSheet.create({...})` is the alternative; both are fine, do
  not mix three systems.
- No `.css` files beyond `global.css`. No cascading, no `:hover`, no
  descendant selectors, no `@media` — use `Pressable`'s state
  (`className="active:opacity-80"` or the `({ pressed }) =>` style prop) and
  `useWindowDimensions()` / `Platform` for conditionals.
- Layout is **Flexbox only**, and `flexDirection` defaults to **`column`**,
  not `row`. `gap` works. There is no `display: grid`, no `float`, no
  `position: sticky`.
- Respect notches: wrap screens in `SafeAreaView` from
  `react-native-safe-area-context` (the template already sets up the
  provider), or use `useSafeAreaInsets()`.

## Lists, images, input — MUST

- Long or data-driven lists: `FlatList` / `SectionList` / `FlashList`, not
  `.map()` inside a `ScrollView`. Give `keyExtractor`.
- Local images: `<Image source={require("../assets/x.png")} />`. Remote images
  need an explicit `width`/`height` (or a sized parent) or they render 0×0.
  For anything real, prefer `expo-image`'s `<Image>` (better caching).
- Text input is `<TextInput>` with `value` + `onChangeText`. There is no
  `<form>` and no native submit — wire a `Pressable` that calls your handler.

## Platform APIs — reach for an Expo module

`expo-image`, `expo-haptics`, `expo-linking`, `expo-constants`,
`expo-clipboard`, `expo-file-system`, `expo-image-picker`, `expo-location`,
`expo-notifications`, `expo-secure-store`, `expo-av`. Install with
`npx expo install <name>` (not plain `npm install` — `expo install` picks the
version that matches the SDK). **`expo-secure-store` is a no-op stub on web
preview** — build against it, but do not tell the user their secret is stored
securely until it runs on a device. Anything that needs a config plugin +
native prebuild (most Bluetooth, some camera/ML modules) is out of scope for
the web preview — say so rather than adding it.

## TypeScript gotchas — MUST

- **Timers are numbers here, not `NodeJS.Timeout`.** There is no `@types/node`
  in an Expo app. Type a timer as `ReturnType<typeof setTimeout>` (or
  `number`), never `NodeJS.Timeout` / `NodeJS.Timer` — that fails
  `tsc --noEmit` with "Namespace 'NodeJS' has no exported member".
- No `process.env` for app config — use `expo-constants`
  (`Constants.expoConfig?.extra`) or `EXPO_PUBLIC_*` vars read via
  `process.env.EXPO_PUBLIC_FOO` (the only `process.env` Metro inlines).
- Event handler types come from `react-native` (`GestureResponderEvent`,
  `NativeSyntheticEvent<…>`, `LayoutChangeEvent`), not from `react`'s DOM
  event types (`MouseEvent`, `ChangeEvent`, `KeyboardEvent`).
- `require()` for a local asset returns a number (an asset id), not a string.

## Scope — MUST

- Build the screens and interaction that were asked for. Do **not** spend the
  turn reverse-engineering assets: decoding PNG frames pixel by pixel with
  inline `node -e` scripts, measuring sprite sheets, or profiling image data
  is almost never the task and it burns the whole step budget. If you need a
  frame count or a dimension, read it once, cheaply, and move on. If an asset
  genuinely can't be used without analysis you cannot do quickly, say so and
  ask — don't grind.

## Definition of done

- `npm run typecheck` is clean (`tsc --noEmit`).
- The Expo web preview renders the real screen on **every** route you added or
  changed — not a blank page, not a red Metro error overlay. Start the
  preview and look.
- No HTML elements, no `document`/`window`, in any `.tsx` under `app/` or
  `components/`.
- New screens are reachable through `expo-router` (a `<Link>`, a tab, or a
  `router.push`), not mounted conditionally inside another screen.