Fetch the Video List
Now that we have a Landing Screen that can pass data to a VideoCard component, the next step is to call an API to retrieve video data that the Landing Screen can use.
We will be using React hooks in order to manage data and state of our application (useState
) and direct the app to retrieve data upon load (useEffect
). Hooks are essentially reusable logic that can be used in your code. We won't be explaining hooks in detail in this guide, but recommend you read more about them (here)https://reactjs.org/docs/hooks-intro.html.
Define the Data
Before we retrieve video data, we need to define what the data will look like in TypeScript.
- Open
LandingScreen.tsx
. - After the import statements, create an interface called
IVideo
with the following definition.
interface IVideo {
id: string;
title: string;
description: string;
duration: number;
thumbURL: string;
imgURL: string;
videoURL: string;
categories: string[];
channel_id: number;
}
Store the Data
Now we need a place to store the video data and a way of updating it that will cause the UI to re-render any component that uses it ("reacting" to events). The useState
hook does both of these things for us. It returns a variable and a function to update the variable. Anytime the variable is updated using the function, it will cause the UI components that use that variable to re-render (they are "reacting" to events).
- In
LandingScreen.tsx
update the import statement for 'react' to includeuseState
. SinceuseState
is a named export, you'll need to include it in curly braces. You'll also need to change the syntax of the statement to avoid errors.
import React, {useState} from 'react';
- At the top of the LandingScreen arrow function, add the following statement:
const [videos, setVideos] = useState<IVideo[]>();
The updated LandingScreen.tsx
code should look like this:
import React, {useState} from 'react';
import Header from '../components/Header';
import VideoCard from '../components/VideoCard';
interface IVideo {
id: string;
title: string;
description: string;
duration: number;
thumbURL: string;
imgURL: string;
videoURL: string;
categories: string[];
channel_id: number;
}
const LandingScreen = () => {
const [videos, setVideos] = useState<IVideo[]>();
return (
<>
<Header />
<VideoCard
title="My Video"
description="My Video Description"
imgURL="https://le1.cdn01.net/videos/0000169/0169322/thumbs/0169322__002f.jpg"
/>
</>
);
};
export default LandingScreen;
Retrieve the Data
Next we need to create a function that will execute our API call. The function will use the Fetch API to call our API endpoint (axios
, another API used for calling other APIs, will be supported in the future). We'll use the setVideo
function to set the array of videos we get back from fetch
. Because fetch
returns a Promise object, we need to handle the code in an asynchronous manner using .then
.
- Create a const in
LandingScreen.tsx
calledurl
, and set it to: https://d2ob7xfxpe6plv.cloudfront.net/TestData.json.
const url = "https://d2ob7xfxpe6plv.cloudfront.net/TestData.json";
- Create an arrow function called
getAllVideos()
in the body of the LandingScreen (i.e. in the LandingScreen arrow function). - Call
fetch
and pass in the url. - Add
.then
to wait for the response, then convert it to json.
const getAllVideos = () => {
fetch(url).then(response => response.json());
};
- Add another
.then
statement to set the value of videos to be the appropriate part of the response object.
const getAllVideos = () => {
fetch(url)
.then(response => response.json())
.then(data => setVideos(data.testData));
};
- Add
.catch
to any error messages and print them using console.log.
Your getAllVideos()
function should look like this:
const getAllVideos = () => {
fetch(url)
.then(response => response.json())
.then(data => setVideos(data.testData))
.catch(error => {
console.log(error);
});
};
(This is common JavaScript and ES6 syntax that uses Promises to retrieve data from an API. The main thing to note is that we're taking data from the API response and calling setVideos
.)
Test Data Retrieval
Now that we have a function that can return and update the list of videos, let's test it out.
- Add a call to
getAllVideos()
anywhere after its definition. - Add console.log statements to log
videos
before and after the call togetAllVideos()
.
console.log(videos);
getAllVideos();
console.log(videos);
-
Reload the application. You'll notice that while the simulator displays correctly, the Metro terminal shows an endless loop of log statements. Log information can be found in different locations depending on how you are running your app:
-
If using VSCode, check the React Native output panel
- If using CLI, look in the Metro terminal
This occurs because every time the videos
variable changes, the LandingScreen is re-rendered. On each re-render, we are retrieving videos again, which causes this endless loop.
console.log(videos); // videos is undefined.
getAllVideos(); // This causes endless re-rendering.
console.log(videos); // This shows us that we are endlessly re-rendering
Add useEffect Hook
To prevent this from happening, we need to put our call to getAllVideos
in a useEffect hook. This hook gives us the ability to only re-render if videos has changed between renderings.
- Update the import statement for 'react' to include
useEffect
. This is a named export, so will go next touseState
in the curly braces.
import React, {useState, useEffect} from 'react';
- Wrap the call to
getAllVideos()
with theuseEffect
hook. This hook takes in an arrow function, where you will callgetAllVideos()
.
console.log(videos);
useEffect(() => {
getAllVideos();
}, []);
console.log(videos);
Note the second parameter is []
. This parameter tells useEffect
to only render once. Since we only need to retrieve the videos once, it works for our purposes. For more advanced ways of using the useEffect
hook, see the React Native documentation.
- Reload the app, and you'll see that the Metro terminal window no longer has an endless loop.
- Remove the console.log() statements now that we have resolved the endless loop.
Create Video Categories
Finally instead of storing all the videos, let's filter the videos into specific categories to display them grouped by category in our UI.
- Modify the state variable to hold our categorized videos instead:
const [islandVideos, setIslandVideos] = useState<IVideo[]>([]);
const [underwaterVideos, setUnderwaterVideos] = useState<IVideo[]>([]);
- Modify the getAllVideos function to filter the videos by category after receiving them from the API using JavaScript's filter() method :
const getAllVideos = () => {
fetch(url)
.then((response) => response.json())
.then((data) => {
const islands = data.testData.filter(
(video: IVideo) =>
video.categories && video.categories.includes('Costa Rica Islands'),
);
const underwater = data.testData.filter(
(video: IVideo) =>
video.categories &&
video.categories.includes('Costa Rica Underwater'),
);
setIslandVideos(islands);
setUnderwaterVideos(underwater);
})
.catch((error) => {
console.log(error);
});
};
Now we have two separate arrays of videos, islandVideos and underwaterVideos, which we can use to display different categories of videos in the UI.
The updated LandingScreen.tsx
should now look like this:
import React, {useState, useEffect} from 'react';
import Header from '../components/Header';
import VideoCard from '../components/VideoCard';
interface IVideo {
id: string;
title: string;
description: string;
duration: number;
thumbURL: string;
imgURL: string;
videoURL: string;
categories: Array<string>;
channel_id: number;
}
const LandingScreen = () => {
const [islandVideos, setIslandVideos] = useState<IVideo[]>([]);
const [underwaterVideos, setUnderwaterVideos] = useState<IVideo[]>([]);
const url = 'https://d2ob7xfxpe6plv.cloudfront.net/TestData.json';
const getAllVideos = () => {
fetch(url)
.then((response) => response.json())
.then((data) => {
const islands = data.testData.filter(
(video: IVideo) =>
video.categories && video.categories.includes('Costa Rica Islands'),
);
const underwater = data.testData.filter(
(video: IVideo) =>
video.categories &&
video.categories.includes('Costa Rica Underwater'),
);
setIslandVideos(islands);
setUnderwaterVideos(underwater);
})
.catch((error) => {
console.log(error);
});
};
useEffect(() => {
getAllVideos();
}, []);
return (
<>
<Header />
<VideoCard
title="My Video"
description="My Video Description"
imgURL="https://le1.cdn01.net/videos/0000169/0169322/thumbs/0169322__002f.jpg"
/>
</>
);
};
export default LandingScreen;