Abfragen von APIs

Abfragen von APIs

Grundwissen:

  • Asynchrones Verhalten in JavaScript
  • Netzwerkanfragen mittels fetch

API-Anfragen in React:

  • Libraries, z.B. react-query, react-swr
  • in Frameworks beinhaltet, z.B. remix
  • eingebauter useEffect-Hook (kann komplex sein)

Netwerkanfragen in JavaScript

Netzwerkanfragen in JavaScript

Promises: moderne Möglichkeit, asynchronen Code zu verwenden:

  • Promises mit async / await
  • Promises mit .then()

Senden von Netzwerkanfragen (basierend auf Promises):

  • fetch

Netzwerkanfragen in JavaScript

asynchrone Funktion, die Todos von einem API lädt:

// todosApi.ts
async function fetchTodos(): Promise<Array<Todo>> {
  const url = 'https://jsonplaceholder.typicode.com/todos';
  const res = await fetch(url);
  if (!res.ok) {
    throw new Error('could not fetch data from network');
  }
  const apiTodos: Array<any> = await res.json();
  // convert data format (don't include userId)
  const todos = apiTodos.map((todo) => ({
    id: todo.id,
    title: todo.title,
    completed: todo.completed,
  }));
  return todos;
}

Netzwerkanfragen in JavaScript

mögliche Hilfsfunktion zum Laden von JSON-Daten:

async function fetchJson(url): Promise<any> {
  const res = await fetch(url);
  if (!res.ok) {
    throw new Error(res);
  }
  const data = await res.json();
  return data;
}

APIs aus React abfragen: grundlegendes Beispiel

APIs aus React abfragen: grundlegendes Beispiel

const [data, setData] = useState(null);
async function loadData() {
  const newData = await fetchData();
  setData(newData);
}
<button onClick={loadData}>
  load some data from an API
</button>

APIs aus React abfragen: grundlegendes Beispiel

Aufgabe: In unserer bestehenden Todo-Anwendung, lade beim Klick eines Buttons Todos von diesem API:

https://jsonplaceholder.typicode.com/todos

Query Libraries

Query Libraries

Funktionalität, die meist gewünscht ist:

  • Mitverfolgen des Lade-Status (loading / success / error)
  • automatisches Senden von Anfragen:
    • wenn eine Komponente zum ersten Mal eingebunden wird
    • wenn sich ein Parameter einer Query geändert hat
  • abbrechen veralteter Anfragen
  • automatisches Caching vergangener Anfragen

Query Libraries

gewünschte Funktionalität ist umsetzbar mittels state, side effects und context (allerdings kompliziert)

... oder mit Hilfe einer Query Library

Query Libraries

Query Libraries:

  • react query
  • swr
  • apollo (für GraphQL APIs)

Query Libraries

Beispiel: Verwalten einer Suchanfrage mit react query

const [searchTerm, setSearchTerm] = useState('foo');

const { status, data } = useQuery({
  queryKey: ['search', searchTerm],
  queryFn: () => fetchJson(`/api/search/${searchTerm}`),
});

Query Libraries

Beispiel: Nachbau von Teilen dieser Funktionalität in reinem React:

const [searchTerm, setSearchTerm] = useState('foo');

const [status, setStatus] = useState('loading');
const [data, setData] = useState<any>(null);

useEffect(() => {
  let ignore = false;
  async function loadData() {
    setStatus('loading');
    setData(null);
    const url = `/api/search/${searchTerm}`;
    try {
      const data = await fetchJson(url);
      if (!ignore) {
        setStatus('success');
        setData(data);
      }
    } catch {
      setStatus('error');
    }
  }
  loadData();
  return () => {
    ignore = true;
  };
}, [searchTerm]);

React Query

React Query

Laden von Daten mit react query:

const [searchTerm, setSearchTerm] = useState('foo');

const { status, data } = useQuery({
  queryKey: ['search', searchTerm],
  queryFn: () => fetchJson(`/api/search/${searchTerm}`),
});

React Query: Setup

npm-Pakete:

  • @tanstack/react-query
  • @tanstack/react-query-devtools

React Query: Setup

globales Setup in index.tsx / main.tsx (umschließt <App />)

import {
  QueryClient,
  QueryClientProvider,
} from 'react-query';
const queryClient = new QueryClient();
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>

React Query: Ladestatus

der Ladestatus ist automatisch verfügbar:

status: loading / success / error

React Query: Ladestatus

typische Verwendung des Ladestatus:

const { status, data } = useQuery(/* ... */);

if (status === 'loading') {
  return <LoadingIndicator />;
} else if (status === 'error') {
  return <ErrorMessage />;
} else {
  return <DataDisplay data={data} />;
}

React Query: Cache

React Query verwendet einen globalen Store/Cache mit Hilfe einer Provider-Komponente

React Query: Keys

jede Query wird durch einen eindeutigen Key identifiziert / mitverfolgt (der mehrere Teile haben kann)

Beispiele für keys in einem Onlineshop:

["product", 123]
["order", 7453]

React Query

Kompliziertere Query, die ein Objekt im Key enthält:

[
  "product-search",
  {
    "category": "phones",
    "maxPrice": 700,
    "color": "black"
  }
]

mögliche API-URL dafür:

/api/product-search?category=phones&maxPrice=700&color=black

React Query

Beispiel: Laden von Wechselkursdaten:

const [from, setFrom] = useState('usd');
const [to, setTo] = useState('eur');

const rateQuery = useQuery({
  queryKey: ['exchange-rate', { from: from, to: to }],
  queryFn: () =>
    fetchJson(
      `https://api.exchangerate.host/convert?from=${from}&to=${to}`
    ),
});

React Query

Anzeigen der Daten:

if (rateQuery.isLoading) {
  return <LoadingIndicator />;
} else if (rateQuery.isError) {
  return <ErrorMessage />;
} else {
  return <RateDisplay data={rateQuery.data} />;
}

React Query

Ãœbung:

Arbeite mit der "Rick and Morty" API und lasse den User Charactere erkunden (durch klicken von next oder previous)

https://rickandmortyapi.com/

React Query: Intermediate

Devtools

aktivieren eines Devtools-Popups während der Entwicklung:

import { ReactQueryDevtools } from 'react-query/devtools';

// ...

  <QueryClientProvider client={queryClient}>
    <App />
    <ReactQueryDevtools initialIsOpen={false} />
  </QueryClientProvider>

Query-Start durch Benutzerinteraktion

standardmäßig: Query startet, sobald eine zugehörige Komponente eingebunden wird, die den Hook aufruft

alternatives Verhalten: Query startet bei einer bestimmten Benutzerinteraktion (z.B. Buttonklick)

Query-Start durch Benutzerinteraktion

Beispiel: State für eine Suchfunktionalität:

// text content of an input (changes when user types)
const [search, setSearch] = useState('');
// active search (changes when user hits enter)
const [submittedSearch, setSubmittedSearch] = useState<
  string | null
>(null);

Query-Start durch Benutzerinteraktion

Aktivieren einer Query, wenn der Benutzer zum ersten mal eine Suche absendet:

const searchQuery = useQuery({
  queryKey: ['search', submittedSearch],
  enabled: submittedSearch !== null,
  queryFn: () =>
    fetchJson(`/api/search/${submittedSearch}`),
});

Mutationen

Definieren von Mutationen:

import { useMutation, useQueryClient } from 'react-query';
import { apiAddTodo } from './todosApi';
// ...
const queryClient = useQueryClient();

function invalidateTodos() {
  queryClient.invalidateQueries({ queryKey: ['todos'] });
}

const addTodoMutation = useMutation({
  mutationFn: apiAddTodo,
  onSuccess: invalidateTodos,
});

// this function is connected to a submit form
function handleAddTodo(newTitle) {
  addTodoMutation.mutate({ title: newTitle });
}
// ...