Kurs React Native · Moduł 4: Stan i dane z API

Fetch API i Axios - Połączenie z Serwerem

3 min czytania ·
W tej lekcji4

Czas nauczyć się komunikacji z zewnętrznymi API - serwerami Atlantydy!

Fetch API - wbudowane rozwiązanie

import { useState, useEffect } from 'react';

interface Creature {
  id: string;
  name: string;
  species: string;
  habitat: string;
}

function CreatureList() {
  const [creatures, setCreatures] = useState<Creature[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    fetchCreatures();
  }, []);

  const fetchCreatures = async () => {
    try {
      setIsLoading(true);
      const response = await fetch('https://api.atlantyda.app/creatures');

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const data = await response.json();
      setCreatures(data);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Wystąpił błąd');
    } finally {
      setIsLoading(false);
    }
  };

  // POST request
  const createCreature = async (creature: Omit<Creature, 'id'>) => {
    try {
      const response = await fetch('https://api.atlantyda.app/creatures', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer token123',
        },
        body: JSON.stringify(creature),
      });

      if (!response.ok) {
        throw new Error('Nie udało się utworzyć stworzenia');
      }

      const newCreature = await response.json();
      setCreatures(prev => [...prev, newCreature]);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Błąd');
    }
  };

  if (isLoading) return <ActivityIndicator size="large" />;
  if (error) return <Text style={styles.error}>{error}</Text>;

  return (
    <FlatList
      data={creatures}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <View style={styles.card}>
          <Text style={styles.name}>{item.name}</Text>
          <Text>{item.species}</Text>
        </View>
      )}
    />
  );
}

Axios - rozbudowana biblioteka

npx expo install axios
// api/client.ts
import axios from 'axios';
import AsyncStorage from '@react-native-async-storage/async-storage';

// Konfiguracja instancji
const apiClient = axios.create({
  baseURL: 'https://api.atlantyda.app',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
});

// Interceptor - dodawanie tokenu do każdego requestu
apiClient.interceptors.request.use(
  async (config) => {
    const token = await AsyncStorage.getItem('authToken');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

// Interceptor - obsługa błędów
apiClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response?.status === 401) {
      // Token wygasł - wyloguj użytkownika
      await AsyncStorage.removeItem('authToken');
      // Przekieruj do logowania
    }
    return Promise.reject(error);
  }
);

export default apiClient;

API Service

// api/creatureService.ts
import apiClient from './client';

export interface Creature {
  id: string;
  name: string;
  species: string;
  habitat: string;
}

export const creatureService = {
  getAll: async (): Promise<Creature[]> => {
    const response = await apiClient.get('/creatures');
    return response.data;
  },

  getById: async (id: string): Promise<Creature> => {
    const response = await apiClient.get(`/creatures/${id}`);
    return response.data;
  },

  create: async (creature: Omit<Creature, 'id'>): Promise<Creature> => {
    const response = await apiClient.post('/creatures', creature);
    return response.data;
  },

  update: async (id: string, updates: Partial<Creature>): Promise<Creature> => {
    const response = await apiClient.patch(`/creatures/${id}`, updates);
    return response.data;
  },

  delete: async (id: string): Promise<void> => {
    await apiClient.delete(`/creatures/${id}`);
  },

  search: async (query: string): Promise<Creature[]> => {
    const response = await apiClient.get('/creatures/search', {
      params: { q: query },
    });
    return response.data;
  },
};

Użycie w komponencie

import { creatureService } from '@/api/creatureService';

function CreatureScreen() {
  const [creatures, setCreatures] = useState<Creature[]>([]);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    loadCreatures();
  }, []);

  const loadCreatures = async () => {
    try {
      setIsLoading(true);
      const data = await creatureService.getAll();
      setCreatures(data);
    } catch (error) {
      if (axios.isAxiosError(error)) {
        console.error('Axios error:', error.response?.data);
      }
    } finally {
      setIsLoading(false);
    }
  };

  const handleCreate = async () => {
    try {
      const newCreature = await creatureService.create({
        name: 'Nowy Trytan',
        species: 'Tryton',
        habitat: 'Głębiny',
      });
      setCreatures(prev => [...prev, newCreature]);
    } catch (error) {
      console.error('Create error:', error);
    }
  };

  return (
    // ... JSX
  );
}

Axios oferuje więcej funkcji niż fetch: interceptory, automatyczne transformacje, anulowanie requestów i lepszą obsługę błędów.

Kod do tej lekcji: api/client.ts
import axios from 'axios';

// Konfiguracja instancji axios
const apiClient = axios.create({
  baseURL: 'https://api.atlantyda.app',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
});

// Symulacja pobierania tokenu (normalnie z AsyncStorage)
async function getAuthToken(): Promise<string | null> {
  return 'token-atlantyda-123';
}

// TODO: dodaj interceptor request, który:
//   - pobiera token przez getAuthToken()
//   - jeśli token istnieje, ustawia config.headers.Authorization = `Bearer ${token}`
//   - zwraca config
apiClient.interceptors.request.use(
  async (config) => {
    const token = await getAuthToken();
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

// TODO: dodaj interceptor response, który przy statusie 401
//   wypisze ostrzeżenie o wygaśnięciu tokenu
apiClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response?.status === 401) {
      console.warn('Token wygasł - wyloguj użytkownika');
    }
    return Promise.reject(error);
  }
);

export default apiClient;

Hasła z dokumentacji w tej lekcji

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Jak poprawnie pobrać dane JSON za pomocą fetch?

  2. 2. Która zaleta NIE dotyczy axios w porównaniu do fetch?

To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Układanie w pionie

    Uporządkuj elementy fetch POST od zewnętrznych do wewnętrznych:

  • Edytor kodu

    Uzupełnij kod apiClient z Bearer token.

Przydatne artykuły