Table of contents
No headings in the article.
Even though I love working with Angular, GraphQL and RxJs, I have been doing it for over 1 year. This means my knowledge of React, REST APIs and HTTP calls is getting very rusty. It is time to step back and freshen up my memory, as most jobs on LinkedIn require their candidates to be experts with it.
Therefore, I built a simple app to retrieve some data from the Star Wars API and display it to the users. If you don't know Swapi, it is a great service to practice API requests and provides a lot of data about Star Wars ๐ In my example, I will focus on the StarWars films, but you can find out more on their website at swapi.dev .
Step 1 - Creating the component's states
In my example, I will need 3 different state variables:
- movie to store the data I get from the API.
- isLoading to display a message to the user whilst the HTTP request is waiting for a response.
- error just in case the HTTP request fails and we need to show the user that there was a problem.
const [movies, setMovies] = useState([])
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState(null)
Step 2 - Create a fetch function
The fetchMovieHandler will contain all the logic to call the API. We can either use the classic method to concat our promises:
const response = fetch('https://swapi.dev/api/films/').then(res => res.json()).then(data => {
// Store our data somewhere
}
).catch(err => {
// this will contain our error handling logic
})
Or we can use async/await witch in my opinion looks tidier (must be wrapped inside a try/catch though):
try {
const response = await fetch('https://swapi.dev/api/films/')
if (!response.ok) {
throw new Error("Something went wrong!")
}
const data = await response.json()
const transformedData = data.results.map(movieData => ({
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date
}))
setMovies(transformedData)
} catch (error) {
setError(error.message)
}
Step 3 - Run this function on the userEffect
I will not pass any dependecies to our useEffect, this means that our function will only run once.
useEffect(() => {
fetchMoviesHandler()
}, [])
Step 4 - Add conditions to our component's content and return the JSX
Below is the complete code. It includes the setState for Error and Loading inside the fetchMoviesHandler function. The useEffect has the fetchMoviesHandler function as a dependency, which will be useful if I need to add some conditional logic to my function and re-render the app. However, to prevent infinite loops (Javascript by default recognizes the fetchMoviesHandler as a new function every time), I wrapped the fetchMoviesHandler inside the useCallback() ensuring the app works correctly.
import React, {useCallback, useEffect, useState} from 'react';
import MoviesList from './components/MoviesList';
import './App.css';
function App() {
const [movies, setMovies] = useState([])
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState(null)
const fetchMoviesHandler = useCallback(async () => {
setIsLoading(true)
setError(null)
try {
const response = await fetch('https://swapi.dev/api/films/')
if (!response.ok) {
throw new Error("Something went wrong!")
}
const data = await response.json()
const transformedData = data.results.map(movieData => ({
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date
}))
setMovies(transformedData)
} catch (error) {
setError(error.message)
}
setIsLoading(false)
}, [])
let content = <p>No movies found.</p>
if (movies.length > 0) {
content = <MoviesList movies={movies}/>
}
if (error) {
content = <p>{error}</p>
}
if (isLoading) {
content = <p>Loading..</p>
}
useEffect(() => {
fetchMoviesHandler()
}, [fetchMoviesHandler])
return (
<>
<section>
<button onClick={fetchMoviesHandler}>Fetch Movies</button>
</section>
<section>
{content}
</section>
</>
);
}
export default App;
You can check my code on: GitHub Repo