Ejemplos de código

Patrones chicos que puedes llevar a tu app. Arregla los imports a tus alias (me gusta @/), mira versiones contra tu SDK y si toca nativo, pruébalo en iOS y Android de verdad.

Cabecera que no se come el notch

react-native-safe-area-context te salva de pintar bajo el notch, el Dynamic Island o la barra de inicio.

import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { View, Text } from 'react-native';

export function ScreenHeader({ title }: { title: string }) {
  const insets = useSafeAreaInsets();
  return (
    <View style={{ paddingTop: insets.top, paddingHorizontal: 16 }}>
      <Text style={{ fontSize: 18, fontWeight: '600' }}>{title}</Text>
    </View>
  );
}

FlatList con carga y “no hay nada”

Spinner al principio, luego filas o un vacío sencillo que el usuario entiende.

import { FlatList, Text, View, ActivityIndicator } from 'react-native';

type Item = { id: string; title: string };

export function ItemList({
  data,
  loading,
}: {
  data: Item[];
  loading: boolean;
}) {
  if (loading && data.length === 0) {
    return (
      <View style={{ flex: 1, justifyContent: 'center' }}>
        <ActivityIndicator />
      </View>
    );
  }
  if (!loading && data.length === 0) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', padding: 24 }}>
        <Text style={{ textAlign: 'center' }}>Nothing here yet.</Text>
      </View>
    );
  }
  return (
    <FlatList
      data={data}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <View style={{ padding: 16 }}>
          <Text>{item.title}</Text>
        </View>
      )}
    />
  );
}

Búsqueda que espera a que dejes de teclear

Debounce corto para no martillar la API en cada tecla; limpieza al desmontar incluida.

import { useEffect, useState } from 'react';
import { TextInput } from 'react-native';

export function SearchField({ onQuery }: { onQuery: (q: string) => void }) {
  const [text, setText] = useState('');

  useEffect(() => {
    const id = setTimeout(() => onQuery(text.trim()), 350);
    return () => clearTimeout(id);
  }, [text, onQuery]);

  return (
    <TextInput
      value={text}
      onChangeText={setText}
      placeholder="Search"
      style={{ borderWidth: 1, borderRadius: 8, padding: 12 }}
    />
  );
}

Tirar para refrescar en un ScrollView

RefreshControl con trabajo async y siempre quita el spinner, aunque la petición falle.

import { useCallback, useState, type ReactNode } from 'react';
import { ScrollView, RefreshControl } from 'react-native';

export function RefreshableScroll({ children }: { children: ReactNode }) {
  const [refreshing, setRefreshing] = useState(false);

  const onRefresh = useCallback(async () => {
    setRefreshing(true);
    try {
      await new Promise((r) => setTimeout(r, 800));
    } finally {
      setRefreshing(false);
    }
  }, []);

  return (
    <ScrollView
      refreshControl={
        <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
      }
    >
      {children}
    </ScrollView>
  );
}

¿Quieres el árbol entero o la referencia del helper enorme? Eso está en estructura y utilidades.

Estructura del proyecto →Utilidades de app →

Patrocinado

Promoción breve