Currently trying to render data that is fetched from an URL. It works fine if I console.log it in the file itself. But now I want to call the fetched data on the page component, so I can render it as TSX.
This is the API file where I fetch the data, called api.ts:
export const getVideoInfo = async () => {
try {
const getVideos = await fetch(
"url"
);
const videos = await getVideos.json();
return videos;
} catch (e) {
console.log(e);
}
};
Then there is another file (here I tried to find a matched with the hash in the url, called useCourseDetail.ts:
import { useLocation } from "react-router";
import { getVideoInfo } from "../../api/Api";
import { Slugify } from "../../components/Util/Slugify";
export const FindCourseDetail = async () => {
const { hash } = useLocation();
const slugifiedHash = Slugify(hash);
const data = await getVideoInfo();
if (hash) {
const video = data.videos.find(
(v) =>
Slugify(v.title, {
lowerCase: true,
replaceDot: "-",
replaceAmpersand: "and",
}) === slugifiedHash
);
return video;
} else {
return data.videos[0];
}
};
And in both files I got the object I want. Now I want to use the content inside the object to render some tsx in the page file, called: CourseDetail.tsx.
import { FindCourseDetail } from "./FindCourseDetail";
type Video = {
title: string;
description?: string;
thumbnail?: string;
videoid?: string;
chapter: boolean;
duration: number;
subtitles: [];
};
export const CourseDetail = () => {
const videoObject = FindCourseDetail();
return (
<div>
<h2>Content player</h2>
<h2>{videoObject.title}</h2>
</div>
);
};
And 'videoObject.title' will give me the error: Property 'title' does not exist on type 'Promise'.
That's fair I think, because if I console.log it, it is a promise. But I'm not sure how I can write it something like this and make it work. So it should be something like: const videoObject = await FindCourseDetail();
. But that's not possible because the component is not async and it's called directly from react router.
I tried to copy paste the function of useCourseDetail inside the CourseDetail. And that works if I use an useState and useEffect. But that's a little bit messy and doesn't feel good, because the default state is an object with null. See code below:
import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import { getVideoInfo } from "../../api/Api";
import { Slugify } from "../../components/Util/Slugify";
type Video = {
title: string;
description?: string;
thumbnail?: string;
videoid?: string;
chapter: boolean;
duration: number;
subtitles: [];
};
export const CourseDetail = () => {
const { hash } = useLocation();
const [videoData, setVideoData] = useState<Video>({
title: null,
description: null,
thumbnail: null,
videoid: null,
chapter: null,
duration: null,
subtitles: null,
});
useEffect(() => {
findCourseData();
}, []);
const findCourseData = async () => {
const slugifiedHash = Slugify(hash);
const data = await getVideoInfo();
if (hash) {
const video = data.videos.find(
(v) =>
Slugify(v.title, {
lowerCase: true,
replaceDot: "-",
replaceAmpersand: "and",
}) === slugifiedHash
);
setVideoData(video);
}
};
return (
<div>
<h2>Content player</h2>
<h2>{videoObject.title}</h2>
</div>
);
};
I have a feeling that this isn't a big problem to solve, but I can't see where it's going wrong or how I can solve this.
Edit: I tried the following:
const [newData, setNewData] = useState({});
const getData = async () => {
try {
const apiValue = await FindCourseDetail();
setNewData(apiValue);
} catch (err) {
// error handling
}
};
getData();
return (
<div>
<h2>Content player</h2>
{newData && <h2>{newData.title}</h2>}
</div>
);
or:
FindCourseDetail().then(videoObject => setNewData(videoObject))
And now it will throw me the same error: Property 'title' does not exist on type '{}'.
If I remove the empty object as default state, it will say: it's possibly undefined.
And if do write null in the default state, it works of course. But I don't think that's the right way:
const [newData, setNewData] = useState({
title: null,
description: null,
thumbnail: null,
videoid: null,
chapter: null,
duration: null,
subtitles: null,
});
Edit 2:
import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import { getVideoInfo } from "../../api/Api";
import { Slugify } from "../../components/Util/Slugify";
type Video = {
title: string;
description?: string;
thumbnail?: string;
videoid?: string;
chapter: boolean;
duration: number;
subtitles: [];
};
export const CourseDetail = () => {
const { hash } = useLocation();
const [videoData, setVideoData] = useState<Video>({
title: null,
description: null,
thumbnail: null,
videoid: null,
chapter: null,
duration: null,
subtitles: null,
});
const findCourseData = async () => {
const slugifiedHash = Slugify(hash);
const data = await getVideoInfo();
if (hash) {
const video = data.videos.find(
(v) =>
Slugify(v.title, {
lowerCase: true,
replaceDot: "-",
replaceAmpersand: "and",
}) === slugifiedHash
);
setVideoData(video);
}
};
useEffect(() => {
findCourseData();
}, []);
return (
<div>
<h2>Content player</h2>
{videoData&& <h2>{videoData.title}</h2>}
</div>
);
};
This currently works, but as you can see I copied and pasted the function itself in the component. So I tried the following code:
type Video = {
title: string;
description?: string;
thumbnail?: string;
videoid?: string;
chapter: boolean;
duration: number;
subtitles: [];
};
export const CourseDetail = () => {
const [newData, setNewData] = useState<Video>(null);
const getData = async () => {
try {
const apiValue = await FindCourseDetail();
console.log(apiValue);
setNewData(apiValue);
} catch (e) {
console.log('catch')
console.log(e)
}
};
useEffect(() => {
getData();
}, []);
return (
<div>
<h2>Content player</h2>
{newData&& <h2>{newData.title}</h2>}
</div>
);
And this won't run, the catch is triggered and this is the error log:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
Not sure why. This is the code of FindCourseDetail that is called:
import { useLocation } from "react-router";
import { getVideoInfo } from "../../api/Api";
import { Slugify } from "../../components/Util/Slugify";
export const FindCourseDetail = async () => {
const { hash } = useLocation();
const slugifiedHash = Slugify(hash);
const data = await getVideoInfo();
if (hash) {
const video = data.videos.find(
(v) =>
Slugify(v.title, {
lowerCase: true,
replaceDot: "-",
replaceAmpersand: "and",
}) === slugifiedHash
);
return video;
} else {
return data.videos[0];
}
};
{newData.title}
}` or `useState()` instead of `useState({})` so that `newData` will return something falsy. – Heretic Monkey Apr 02 '21 at 13:37