Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Fetching data from an API

Real apps take data from a server. React Native has the same fetch function as the browser, and, as on the web, the request is sent in useEffect. The phone adds its own details: a system loading indicator and pull-to-refresh on lists.

The post list

The address https://jsonplaceholder.typicode.com/posts returns 100 posts with id, title and body fields. Replace the local array with loading in app/(tabs)/index.tsx:

// app/(tabs)/index.tsx
import { Link } from 'expo-router';
import { useEffect, useState } from 'react';
import { ActivityIndicator, FlatList, Text, View } from 'react-native';
import type { Post } from '@/data/posts';
 
const API = 'https://jsonplaceholder.typicode.com';
 
export default function Posts() {
  const [posts, setPosts] = useState<Post[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
 
  async function load() {
    setLoading(true);
    setError(null);
    try {
      const response = await fetch(`${API}/posts`);
      if (!response.ok) throw new Error(`Error ${response.status}`);
      setPosts(await response.json());
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error');
    } finally {
      setLoading(false);
    }
  }
 
  useEffect(() => {
    load();
  }, []);
 
  if (loading && posts.length === 0) {
    return <ActivityIndicator size="large" style={{ flex: 1 }} />;
  }
 
  if (error) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', gap: 12 }}>
        <Text>Could not load posts: {error}</Text>
        <Text onPress={load} style={{ color: '#2563eb' }}>Retry</Text>
      </View>
    );
  }
 
  return (
    <FlatList
      data={posts}
      keyExtractor={(item) => String(item.id)}
      refreshing={loading}
      onRefresh={load}
      renderItem={({ item }) => (
        <Link href={{ pathname: '/posts/[id]', params: { id: item.id } }} style={{ padding: 16 }}>
          {item.title}
        </Link>
      )}
    />
  );
}

First the system ActivityIndicator spins in the center, then a list of 100 titles appears. The Post type still comes from data/posts.ts.

Pull to refresh

The refreshing and onRefresh props enable pull-to-refresh on a FlatList: pull the list down, an indicator appears, and load fetches the posts again. While refreshing is true, the indicator spins.

Error and retry

Turn on airplane mode and reload the app with the r key: an error message appears instead of the list. Turn airplane mode off and tap "Retry", and the posts load. Connections drop often on a phone, so a retry button is almost always needed.

The post screen

The app/posts/[id].tsx screen loads a single post from ${API}/posts/${id} the same way: post, loading and error state and a useEffect with the [id] dependency. A post's comments are at /posts/1/comments.

Common mistake

Calling localhost from the phone. If you run your own API on the computer at http://localhost:3000, then on the phone localhost is the phone itself, and the request never arrives. Use the computer's IP address on the local network, for example http://192.168.1.10:3000. Also, iOS blocks plain http requests by default, so a real server needs https.

Thanks for reading!

Donate to the site — we work hard for you.