gistmax. Back to DiscoverStudio
Technology

App development tutorial: load an API list with loading, error and retry states

By Gistmax Tutorials · 2026-11-04

App development tutorial workflow: Start request, Validate data, Render list, Retry failures.
App development tutorial workflow: Start request, Validate data, Render list, Retry failures.

Build a small mobile reader that fetches demonstration tasks over HTTPS. A useful network screen needs more than a successful response: it should explain loading, handle failures and cancel work when a request is no longer relevant.

1. Prepare the app

Create a blank TypeScript Expo project using `npx create-expo-app@latest gistmax-api-reader --template blank-typescript`. Replace `App.tsx` with the code below, then run `npx expo start`.

The sample uses JSONPlaceholder, a public demonstration API. Its tasks are fictional. It requires an internet connection but no private key. Do not put backend secrets in a mobile application; people can inspect distributed app code.

2. Implement the request lifecycle

import React, { useEffect, useState } from 'react';
import { SafeAreaView, Text, Button, FlatList, ActivityIndicator } from 'react-native';

type Task = { id: number; title: string; completed: boolean };

export default function App() {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');
  const [attempt, setAttempt] = useState(0);
  useEffect(() => {
    const controller = new AbortController();
    setLoading(true); setError('');
    async function load() {
      try {
        const response = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=10', {
          signal: controller.signal,
        });
        if (!response.ok) throw new Error('The service could not load the list.');
        const data: unknown = await response.json();
        if (!Array.isArray(data) || !data.every(item =>
          item && typeof item.id === 'number' && typeof item.title === 'string'
          && typeof item.completed === 'boolean')) throw new Error('Unexpected response format.');
        if (!controller.signal.aborted) setTasks(data);
      } catch (failure) {
        if (!controller.signal.aborted) setError(failure instanceof Error ? failure.message : 'Request failed.');
      } finally {
        if (!controller.signal.aborted) setLoading(false);
      }
    }
    load();
    return () => controller.abort();
  }, [attempt]);
  return (
    <SafeAreaView style={{ flex: 1, padding: 24 }}>
      <Text style={{ fontSize: 28, fontWeight: '700' }}>Demo task reader</Text>
      {loading ? <ActivityIndicator accessibilityLabel="Loading tasks" /> : error ? <>
        <Text accessibilityRole="alert">{error}</Text>
        <Button title="Try again" onPress={() => setAttempt(value => value + 1)} />
      </> : <FlatList data={tasks} keyExtractor={item => String(item.id)}
        ListEmptyComponent={<Text>No tasks were returned.</Text>}
        renderItem={({ item }) => <Text style={{ paddingVertical: 16 }}>
          {item.completed ? '✓ ' : '○ '}{item.title}
        </Text>} />}
    </SafeAreaView>
  );
}

3. Test success and failure

With a working connection, the list should display ten tasks. Turn off the device's connection before opening the screen again; you should see an error and a retry button. Restore the connection and retry. A request may take time to fail, so do not assume the screen is broken merely because it has not failed immediately.

The cleanup function aborts the request when the effect is replaced or the component leaves the screen. The checks before state updates also prevent an obsolete request from overwriting the result of a newer attempt.

4. Adapt the reader to your own backend

When switching to Gistmax, use its actual response shape rather than assuming it returns a bare array. A physical phone's `localhost` refers to the phone, not your development computer. Use the deployed HTTPS API or an appropriate development-network address.

For the next exercise, add an explicit timeout and pagination. Test a malformed response as well as a disconnected device. Fetching JSON successfully does not prove that its fields match the interface your screen expects.

References

React Native networking · React useEffect · JSONPlaceholder guide

Join the conversation →