Await result of API call and update list component - javascript

I am calling a REST API and return chat rooms. Those get returned fine, as I can see in the payload of the object via the console.
Now I want to display this in a list.
I was able to display a list and download the chat rooms, but not combine both. This is what I did:
import * as React from 'react';
export default function ChatRoomList({param1}) {
var x = getChatRoom(param1)
console.log(x)
return (
<div>
<li>{param1}</li>
<li> asd </li>
<li> asd </li>
</div>
);
}
async function getChatRoom(status) {
// status could be either 'open' or 'room'
var dict = {
chatType: status,
};
var adminChats = await callKumulosAPIFunction(dict, 'getAdminChat')
return adminChats
}
Now I did try to simply await the first getChatRoom(param1) call. But I can only await inside an async function. I then tried to add the async keyword to the export function, but this would crash the whole app.
How would the workflow be here? And how would I map the result from getChatRoom onto a listview?
The result of the adminChats (console.log(adminChats)):

You need to use useEffect hook to get remote data:
export default function ChatRoomList({param1}) {
React.useEffect(()=>{
(async () => {
var x = await getChatRoom(param1)
console.log(x)
})()
},[])
return (
<div>
<li>{param1}</li>
<li> asd </li>
<li> asd </li>
</div>
);
}
If the data returned from getChatRoom is an array and you want to show it, you need to save the response in a state, or the component will not re-render:
export default function ChatRoomList({param1}) {
const [chatRooms, setChatRooms] = React.useState([]);
React.useEffect(()=>{
(async () => {
var x = await getChatRoom(param1)
setChatRooms(x)
})()
},[])
return (
<div>
<li>{param1}</li>
{chatRooms.map((chatRoom , index) => {
return <li key={index}>{JSON.stringify(chatRoom)}</li>
})}
</div>
);
}
async function getChatRoom(status) {
// status could be either 'open' or 'room'
var dict = {
chatType: status,
};
var adminChats = await callKumulosAPIFunction(dict, 'getAdminChat')
return adminChats.payload
}
I suggest you to read the documentation about React hooks and lifecycle management:
https://reactjs.org/docs/hooks-intro.html
https://reactjs.org/docs/state-and-lifecycle.html

You need to use useEffect hook to call the API.
Read more about hooks
import * as React from 'react';
function getChatRoom(status) {
const [chats, setChats] = React.useState([]);
const getChart = async (status) => {
// status could be either 'open' or 'room'
const dict = {
chatType: status,
};
const adminChats = await callKumulosAPIFunction(dict, 'getAdminChat');
setChats(adminChats);
};
React.useEffect(() => {
getChart(status)
}, [status])
return { chats };
};
export default function ChatRoomList({param1}) {
const { chats } = getChatRoom(param1)
console.log(chats)
return (
<div>
<li>{param1}</li>
{chats?.map((chatRoom , index) => {
return <li key={index}>{JSON.stringify(chatRoom)}</li>
})}
</div>
);
}
Dependencies argument of useEffect is useEffect(callback, dependencies)
Let's explore side effects and runs:
Not provided: the side-effect runs after every rendering.
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs after EVERY rendering
});
}
An empty array []: the side-effect runs once after the initial rendering.
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs ONCE after initial rendering
}, []);
}
Has props or state values [prop1, prop2, ..., state1, state2]: the side-effect runs only when any dependency value changes.
import { useEffect, useState } from 'react';
function MyComponent({ prop }) {
const [state, setState] = useState('');
useEffect(() => {
// Runs ONCE after initial rendering
// and after every rendering ONLY IF `prop` or `state` changes
}, [prop, state]);
}

Related

How to use data of an Async function inside a functional component to render HTML in React

I've been trying to use the data I get from an Async function inside of another function I use to display HTML on a react project. I have made several attempts but nothing seems to work for me. Hope any of you could help me. Please correct me if I did anything wrong.
I've tried it with a useEffect as well:
import React, { useState, useEffect } from 'react';
import { getGenres } from './api/functions';
const ParentThatFetches = () => {
const [data, updateData] = useState();
useEffect(() => {
const getData = async () => {
const genres = await getGenres('tv');
updateData(genres);
}
getData();
}, []);
return data && <Screen data={data} />
}
const Screen = ({data}) => {
console.log({data}); //logs 'data: undefined' to the console
return (
<div>
<h1 className="text-3xl font-bold underline">H1</h1>
</div>
);
}
export default Screen;
The Error I get from this is: {data: undefined}.
The getGenres function that makes the HTTP Request:
const apiKey = 'key';
const baseUrl = 'https://api.themoviedb.org/3';
export const getGenres = async (type) => {
const requestEndpoint = `/genre/${type}/list`;
const requestParams = `?api_key=${apiKey}`;
const urlToFetch = baseUrl + requestEndpoint + requestParams;
try {
const response = await fetch(urlToFetch);
if(response.ok) {
const jsonResponse = await response.json();
const genres = jsonResponse.genres;
return genres;
}
} catch(e) {
console.log(e);
}
}
I want to use the data inside my HTML, so the H1 for example.
Once again, haven't been doing this for a long time so correct me if I'm wrong.
There are a few conceptual misunderstandings that I want to tackle in your code.
In modern React, your components should typically render some type of jsx, which is how React renders html. In your first example, you are using App to return your genres to your Screen component, which you don't need to do.
If your goal is to fetch some genres and then ultimately print them out onto the screen, you only need one component. Inside that component, you will useEffect to call an asynchronous function that will then await the api data and set it to a react state. That state will then be what you can iterate through.
When genres is first rendered by react on line 6, it will be undefined. Then, once the api data is retrieved, React will update the value of genre to be your array of genres which will cause the component to be re-rendered.
{genres && genres.map((genre) ... on line 20 checks to see if genres is defined, and only if it is, will it map (like looping) through the genres. At first, since genres is undefined, nothing will print and no errors will be thrown. After the genres are set in our useEffect hook, genres will now be an array and we can therefore loop through them.
Here is a working example of your code.
import React, { useState, useEffect } from "react";
import { getGenres } from "./api/functions";
function App() {
const [genres, setGenres] = useState();
useEffect(() => {
async function apiCall() {
const apiResponse = await getGenres("tv");
console.log(apiResponse);
setGenres(apiResponse);
}
apiCall();
}, []);
return (
<div>
<h1 className="text-3xl font-bold underline">H1</h1>
{genres && genres.map((genre) => <div key={genre}>{genre}</div>)}
</div>
);
}
export default App;
You should use a combination or useEffect and useState
You should use useEffect to launch the async get, without launching it each rerendering. If a async function is used to get some data from outside the component, this is called 'side effect' so use useEffect.
You should use useState to react to changes on theses side effects, re-rendering the component to get the data in the dom.
In the next example, Im using a dummy async function getGenres which returns an array of genres.
Here is an example and a WORKING EXAMPLE :
const {useState, useEffect} = React;
async function getGenres() {
var promise = new Promise(function(resolve, reject) {
window.setTimeout(function() {
resolve( ['genre1', 'genre2']);
});
});
return promise;
}
const Screen = () => {
const [genres, setGenres] = useState([])
useEffect(
() => {
getGenres().then(
res => setGenres(res)
)
}, [getGenres]
)
return (
<ul>
{
genres.map(
i => <li>{i}</li>
)
}
</ul>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<Screen/>)

How to prevent set state before useEffect?

I have a React/Next component. This component download data from firebase storage based on a route. For example, for route http://localhost:3000/training/javascript the component with get data from /training/javascript router in firebase storage.
// ReactJS
import { useState, useEffect } from "react";
// NextJS
import { useRouter } from "next/router";
// Seo
import Seo from "../../../components/Seo";
// Hooks
import { withProtected } from "../../../hook/route";
// Components
import DashboardLayout from "../../../layouts/Dashboard";
// Firebase
import { getDownloadURL, getMetadata, listAll, ref } from "firebase/storage";
import { storage } from "../../../config/firebase";
// Utils
import prettysize from "prettysize";
import capitalize from "../../../utils/capitalize";
import { PlayIcon } from "#heroicons/react/outline";
import { async } from "#firebase/util";
function Video() {
// States
const [videos, setVideos] = useState([]);
// Routing
const router = useRouter();
const { id } = router.query;
// Reference
const reference = ref(storage, `training/${id}`);
useEffect(() => {
const fetchData = async () => {
let tempVideos = [];
let completeVideos = [];
const videos = await listAll(reference);
videos.items.forEach((video) => {
tempVideos.push(video);
});
tempVideos.forEach((video) => {
getMetadata(ref(storage, video.fullPath)).then((metadata) => {
completeVideos.push({
name: metadata.name,
size: prettysize(metadata.size),
});
});
});
tempVideos.forEach((video) => {
getDownloadURL(ref(storage, video.fullPath)).then((url) => {
completeVideos.forEach((completeVideo) => {
if (completeVideo.name === video.name) {
completeVideo.url = url;
}
});
});
});
setVideos(completeVideos);
};
fetchData();
}, [id]);
console.log("Render", videos)
return (
<>
<Seo
title={`${capitalize(id)} Training - Dashboard`}
description={`${capitalize(
id
)} training for all Every Benefits Agents.`}
/>
<DashboardLayout>
<h2>{capitalize(reference.name)}</h2>
<ul>
{videos.map((video) => {
return (
<li key={video.name}>
<a href={video.url} target="_blank" rel="noopener noreferrer">
<PlayIcon />
{video.name}
</a>
<span>{video.size}</span>
</li>
);
})}
</ul>
</DashboardLayout>
</>
);
}
export default withProtected(Video);
I have an useState that should be the array of videos from firebase. I use an useEffect to get the data from firebase and extract the needed information. Some medatada and the url.
Everything's fine. The information is extracted, and is updated to the state correctly. But when the state is updated, it's no showings on the screen.
This is a console.log of the videos state updated, so you can see it's correctly updated.
You messed up a bit with asynchronous code and loops, this should work for you:
useEffect(() => {
const fetchData = async () => {
try {
const videos = await listAll(reference);
const completeVideos = await Promise.all(
videos.items.map(async (video) => {
const metadata = await getMetadata(ref(storage, video.fullPath));
const url = await getDownloadURL(ref(storage, video.fullPath));
return {
name: metadata.name,
size: prettysize(metadata.size),
url,
};
})
);
setVideos(completeVideos);
} catch (e) {
console.log(e);
}
};
fetchData();
}, []);
Promise.all takes an array of promises, and returns a promise that resolves with an array of all the resolved values once all the promises are in the fulfilled state. This is useful when you want to perform asynchronous operations like your getMetaData and getDownloadURL, on multiple elements of an array. You will use .map instead of .forEach since map returns an array, while forEach does not. By passing an async function to .map, since an async function always returns a Promise, you are basically creating an array of promises. and that's what you can feed Promise.all with.
That's it, now it just waits that all the async calls are done and you can just await for Promise.all to resolve.

Browser freezing when using React Hook

When I enter into a router that refers to this Component, my browser just crashes. I've made some console tests and notices that when the response.data.message is up, it continually re-renders the page. Can someone help me?
import React from 'react'
import "./UsernameStory.css";
import Axios from "axios";
const UsernameStory = ({match}) => {
const [statue , setStatue] = React.useState("");
const [stories , setStories] = React.useState([]);
const fetchUsername = () => {
const urls = match.params.username;
Axios.post("http://localhost:8080/"+urls, {
}).then((response) => {
if(response.data.statue)
{
setStatue(response.data.statue);
}
if(response.data.message){
setStories(response.data.message);
}
})
}
return (
<div>
{fetchUsername()}
<p>{statue}</p>
<ul>
{stories.map((story , key) => (<li key={key}>{story.username}</li>))}
</ul>
</div>
)
}
export default UsernameStory
On every render, fetchUsername() is called and results in updating statue and stories, which results in another rerender, and thus leads to an infinite loop of rerendering (since every render triggers a state update).
A better practice for handling functions with side-effects like fetching data is to put the fetchUsername in useEffect.
const UsernameStory = ({match}) => {
const [statue , setStatue] = React.useState("");
const [stories , setStories] = React.useState([]);
useEffect(() => {
const urls = match.params.username;
Axios.post("http://localhost:8080/"+urls, {})
.then((response) => {
if(response.data.statue)
{
setStatue(response.data.statue);
}
if(response.data.message){
setStories(response.data.message);
}
});
}, []); // An empty denpendency array allows you to fetch the data once on mount
return (
<div>
<p>{statue}</p>
<ul>
{stories.map((story , key) => (<li key={key}>{story.username}</li>))}
</ul>
</div>
)
}
export default UsernameStory
You can't call function fetchUsername() inside return statement. It
will go in infinite loop.
You can't directly set the state of two variable as setStatue(response.data.statue) & setStories(response.data.message) respectively
use the useEffect hooks and set the status inside that hooks with conditional rendering to avoid looping.
call the fetchUsername() just before the return statement.
I looked at your code carefully. As you can see in this code, the fetchUsername() function is executed when the component is first rendered.
When you call setState() in the fetchUsername function, your component's state variable is updated. The problem is that when the state variable is updated, the component is rendered again. Then the fetchUsername function will be called again, right?
Try the useEffect Hook.
The example code is attached.
eg code
How about trying to change the way you call the fetchUsername() function with the useEffect hook instead?
import React, { useEffect } from 'react'
import "./UsernameStory.css";
import Axios from "axios";
const UsernameStory = ({match}) => {
const [statue , setStatue] = React.useState("");
const [stories , setStories] = React.useState([]);
useEffect(() => {
const fetchUsername = () => {
const urls = match.params.username;
Axios.post("http://localhost:8080/"+urls, {
}).then((response) => {
if(response.data.statue)
{
setStatue(response.data.statue);
}
if(response.data.message){
setStories(response.data.message);
}
})
}
fetchUsername();
// clean up here
return () => {
// something you wanna do when this component being unmounted
// console.log('unmounted')
}
}, [])
return (
<div>
<p>{statue}</p>
<ul>
{stories.map((story , key) => (<li key={key}>{story.username}</li>))}
</ul>
</div>
)
}
export default UsernameStory
But, sometimes your code just doesn't need a cleanup in some scenarios, you could read more about this here: Using the Effect Hook - React Documentation.

logging the data but not rendering p tag , why?

I am using firebase firestore and i fetched the data , everything is working fine but when i am passing it to some component only one item gets passed but log shows all the elements correctly.
I have just started learning react , any help is appreciated.
import React, { useEffect, useState } from 'react'
import { auth, provider, db } from './firebase';
import DataCard from './DataCard'
function Explore() {
const [equipmentList, setEquipments] = useState([]);
const fetchData = async () => {
const res = db.collection('Available');
const data = await res.get();
data.docs.forEach(item => {
setEquipments([...equipmentList, item.data()]);
})
}
useEffect(() => {
fetchData();
}, [])
equipmentList.forEach(item => {
//console.log(item.description);
})
const dataJSX =
<>
{
equipmentList.map(eq => (
<div key={eq.uid}>
{console.log(eq.equipment)}
<p>{eq.equipment}</p>
</div>
))
}
</>
return (
<>
{dataJSX}
</>
)
}
export default Explore
You have problems with setting fetched data into the state.
You need to call setEquipments once when data is prepared because you always erase it with an initial array plus an item from forEach.
The right code for setting equipment is
const fetchData = async () => {
const res = db.collection('Available');
const data = await res.get();
setEquipments(data.docs.map(item => item.data()))
}

How to set the default value to props

The below code fetch the URL data and retrieve All the film list with the duration in seconds
But I want to list out only the films which have duration more than 5000 secs
*The component should have a prop minDuration and only films with a duration more than this value should be listed; the default value for this prop should be 5000
Could you please clarify how to solve this
import React, { useState, useEffect } from "react";
export default function App() {
const [films, setFilms] = useState([]);
useEffect(() => {
const listOfFilms = async () => {
const response = await fetch(
"https://api.flixpremiere.com/v1/films/filter/now_showing?limit=10"
);
console.log("here", response.data);
const jsonresponse = await response.json();
console.log("here11", jsonresponse.films);
setFilms(jsonresponse.films);
};
listOfFilms();
}, []);
return (
<div className="App">
<h1>Film Title</h1>
{console.log("films", films.slug, films.films)}
{films.map((film, index) => (
<ul>
<li key={film.title}>
{film.title} {`(${film.duration_seconds})`} </li>
</ul>
))}
</div>
);
}
I'm going to answer the question as it's written, which is "How to set the default value to props".
const Component = ({ property = 5000 }) => { ... }
If you want to only show certain items based on criteria, use the filter function.
The only one way is use default parameter in your function component.
const Component = ({ iNeedDefaultValue = "foo" }) => { ... }
The rest of is deprecated (words of "father of react").
Because defaultProps on functions will eventually get deprecated.
See: https://twitter.com/dan_abramov/status/1133878326358171650
for setting default props for a component, you can use of defaultProps property
class Tooltip extends React.Component {
// ...
}
Tooltip.defaultProps = {
delay: 100,
}

Categories

Resources