using a JSX react variable after completion of a Promise - javascript

Here is the code:
import React from 'react';
import { Link } from 'react-router-dom';
import Main from '../layouts/Main';
import Cell from '../components/Stats/nCell';
import init from '../data/stats';
const promiseHandle = async () => {
Window.data = await init();
};
promiseHandle();
const Stats = () => (
<Main
title="Stats"
description="Some statistics about Ahammad Shawki and ahammadshawki8.github.io"
>
<article className="post" id="stats">
<header>
<div className="title">
<h2 data-testid="heading"><Link to="/stats">Publications</Link></h2>
</div>
</header>
<p className="rec-p"> Please accept my sincere apologies for the inconvenience.
At the moment, I am working on my Publication API for this website
so may not be able to find all of my articles here.
But the great news is that you can always read all of my articles on HashNode.
</p>
</article>
{Window.data.map((post) => (
<Cell
data={post}
key={post.title}
/>
))}
</Main>
);
export default Stats;
Since I am working with a promise, here Window.data has been declared async way. But in the react JSX {}, the code is sync. So, it runs before the completion of the promise which causes the bug. I want to run it after the promise. How to solve this? Thanks in advance.

You need to either move the call inside the component and use useEffect and useState or do a poll inside the component.
const Stats = () => {
const [data, setData] = useState([])
useEffect( () => {
const promiseHandle = async () => {
setData(await init());
};
promiseHandle();
}, [])

Related

After refreshing, React props turn undefined

I'm fetching from an API where I want to pass the response object as a prop onto a child component from App.js and use it in my Tags.js file. However, it only works after one time and when I refresh it, it gives me an error saying the props.response.names.length is undefined. I tried using the useEffect function to try and update it but it didn't work. I would appreciate any help.
My App.js file (still some remnants of when you run "npx create-react-app my-app"):
import './App.css';
import Tags from './Tags.js';
import React, { useState, useEffect } from 'react';
function App() {
const makeRequest = async () => {
try {
let response = await fetch('RANDOM_API_URL');
let json = await response.json();
setResponse(json);
} catch (error) {
console.log(error);
}
}
const [response, setResponse] = useState(makeRequest);
useEffect(() => {
setResponse(makeRequest);
}, []);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<Tags response={response}></Tags>
</div>
);
}
export default App;
My Tags.js:
import './App.js';
function Tags(props) {
const makeButtons = () => {
let result = [];
for (let i = 0; i < props.response.names.length; i++) {
result.push(<button key={i}>hello</button>);
}
return result;
}
return (
<div>
{makeButtons()}
</div>
);
}
export default Tags;
Your makeRequest function is async, and sets state internally. The reason you get this bug is that you don't always have a response.names to read length from - you only have an empty response object.
Either make sure you always have the names array available in state, or avoid rendering your Tags component when names is not present in state.
Also, try to avoid being creative with your dependency array, it's there for a reason. I see why you didn't include makeRequest in it though, since you create a new function on every render. That's something to keep in mind when you transition from class components to functional components. Class methods are stable across renders, functions declared in a functional component are not. To mimic a class method, you can declare functions using the useCallback hook, but again you need to include the dependency array. In your case, you can just create the async function inside useEffect, and then call it.
const [response, setResponse] = useState({names: []});
// or const [response, setResponse] = useState();
// and then, in Tags:
// function Tags({response = {names: []}) {
useEffect(() => {
const makeRequest = async () => {
try {
let response = await fetch('RANDOM_API_URL');
let json = await response.json();
setResponse(json);
} catch (error) {
console.log(error);
}
}
makeRequest();
}, []);
Supplying a default response prop to Tags will make sure you can read the length of names even before you have a response.
Looking more closely on your Tags component, I think it should be something like this:
// don't import App.js here
export default function Tags({response = {names: []}){
return (
<div>
{response.names.map(name => {
return <button key={name}>Hello {name}</button>
})}
</div>
)
}
Don't use index as key, that will cause problems if you rearrange the names in your array. And I guess you want to supply an onClick function to your buttons.

React not getting API results on first render

I'm running into a problem in development where the page finishes loading before the data gets sent from the API. I've tried using asynchronous functions but that doesn't help even though I'm sure it should. I think I might be doing it wrong. Below is an example of a page in my app where I am experiencing this issue:
import React, {useEffect, useState} from 'react';
import { useRouter } from 'next/router';
import axios from 'axios';
import Link from 'next/link';
import { Card,
Button
} from 'react-bootstrap';
export default function SingleTour() {
const [tour, setTour]= useState({});
const [tourShows, setTourShows] = useState({});
const router = useRouter();
const {slug} = router.query;
useEffect( () => {
let enpoints = [
`http://localhost:3000/tours/${slug}`,
`http://localhost:3000/listshows/${slug}`
]
axios.all(
enpoints.map((endpoint) =>
axios.get(endpoint)))
.then(response => {
console.log(response)
setTour(response[0].data)
setTourShows(response[1].data)
})
.catch(error => console.log(error))
}, [slug])
console.log(tour);
return (
<div className='container'>
<div>
<h1></h1>
</div>
<h3>Shows</h3>
<div className='card-display'>
{tourShows.data ? (
tourShows.data.map(({attributes, id}) => (
<Link href={`/shows/${id}`} passHref key={id}>
<Card border="secondary" style={{ width: '18rem', margin: '1rem'}}>
<Card.Body>
<Card.Title>Show {id}</Card.Title>
<Card.Text>{attributes.date}</Card.Text>
<Card.Text>{attributes.location}</Card.Text>
<Card.Text>Head Count {attributes.headcount}</Card.Text>
</Card.Body>
</Card>
</Link>
))
) : 'LOADING ...'}
</div>
</div>
)
}
Any help is greatly appreciated. I am also using Next JS if that makes a difference.
If you use useEffect hook it is expected that you will have a render before the hook fires to fetch the data, that is the way useEffect works.
If you want to fetch your data inside the next app you have to use getServerSideProps instead, fetch the data there and pass that as a prop to the component. See the docs here: https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props
This is the way React works. useEffect will attempt to fetch the data and React will continue doing it's business, render the component. You can put an if statement at the beginning of the return statement, for instance checking the length of the tourShows.data, it the length is 0 return nothing, otherwise return as you do now.

This code doesn't show a compile error, but the console won't render

import React from 'react';
const url = 'https://randomuser.me/api/?results=10'
async function List() {
const data = await fetch (url)
const response = await data.json()
return (
<div>
{response.map((item)=>(
<div>{item.results[1].name.first}</div>
))}
</div>
)
}
It also throws this: The above error occurred in the component:
at List
at div
at App
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
If you working with a functional component. You need to use React hooks for API calls. You can use useEffect hooks to call the API.
Example:
const { useState } = React;
function useFetchData() {
const [data, setData] = React.useState([]);
React.useEffect(() => {
return fetch("https://randomuser.me/api/?results=10")
.then((response) => response.json())
.then((responseJson) => {
setData(responseJson.results);
})
.catch((error) => {
console.error(error);
});
}, []);
return { data };
}
function App() {
const { data } = useFetchData();
return (
<div id="quote-box">
{data.map((item) => (
<div>{item.name.first}</div>
))}
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Calling an API inside a component is a side effect and React doesn't want that. React components expect you to provide the data and render that data immediately. If you want to call an API before rendering the component, use a side effect method like useEffect() and call that API inside the lambda. The component now knows that there is an expected side effect and will re-render once the data has updated.

Show a skeleton placeholder until Facebook Comments component loads completely in React (Next.js)

Using Next.js, I want to show a skeleton placeholder until Facebook Comments component loads completely.
Here is the code.
import { useState, useEffect } from "react";
import { initFacebook } from "../utils/initFacebook";
export default function IndexPage() {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
const loadFacebook = async () => {
await initFacebook();
setLoaded(true);
};
loadFacebook();
}, []);
const skeletonComponent = (
<div>
<h1>Some skeleton placeholder</h1>
</div>
);
const facebookComponent = (
<div
className="fb-comments"
data-href="https://developers.facebook.com/docs/plugins/comments#configurator"
data-width="580"
data-numposts="10"
/>
);
return (
<div>
{loaded ? facebookComponent : skeletonComponent}
</div>
);
}
I'm using the state to switch between two components.
But the skeleton component does not wait until the Facebook component is fully loaded, and therefore users see the blank screen for about 3-5 seconds.
How should I go about having the skeleton component wait out until the Facebook component is visible?
The full code is available on CodeSandbox.
Any help would be appreciated.

Persist data with localStorage - React.js

I'm confused about how to use localStorage to persist the data that's coming from calling the API.
I want whenever I refresh the page, the callApi inside useEffect to not render new data and keep the existing data unchanged.
Any help would be appreciated.
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { Layout, Loading, OverviewHeader, OverviewSubHeader, SiteCard } from '../components';
const Overview = () => {
const [loading, setLoading] = useState(true);
const [sites, setSites] = useState([]);
useEffect(() => {
async function callApi() {
const response = await axios.get(`https://randomuser.me/api/?results=3`);
const sites = response?.data?.results;
console.log('sites', sites);
setSites(sites);
await localStorage.setItem('sites', JSON.stringify(sites));
setLoading(false);
}
callApi();
}, []);
return (
<div>
<Layout>
<OverviewHeader />
<OverviewSubHeader />
<div className='overview-page-wrapper'>
{loading ? (
<Loading />
) : (
sites.map(site => {
return (
<React.Fragment>
<SiteCard
key={site.login.uuid}
siteId={site.login.uuid}
image={site.picture.large}
firstName={site.name.first}
lastName={site.name.last}
city={site.location.city}
country={site.location.country}
sensors={site.dob.age}
notifications={site.registered.age}
latitude={site.location.coordinates.latitude}
longitude={site.location.coordinates.longitude}
{...site}
/>
</React.Fragment>
);
})
)}
</div>
</Layout>
</div>
);
};
export default Overview;
I'm not too sure what you're trying to accomplish, seeing as you'd likely want to refresh that data at some point.
Maybe you could indicate what behaviour/scenario you're trying to cater for?
In any case, to answer your question, what you could do is smth like:
const [displayedSites, setDisplayedSites] = useState([])
// this does both setting the state for your UI
// and stores to localStorage
const setAndSaveDisplayedSites = (fetchedSites) => {
setDisplayedSites(sites)
localStorage.setItem('sites', JSON.stringify(sites))
}
useEffect(() => {
(async function () {
const localSites = localStorage.getItem(sites);
if (!localSites) {
// this will only ever fetch if it is your first time mounting this component
// I suppose you would need to call setAndSaveDisplayedSites
// from a "refresh" button
const fetchedSites = await getSitesFromAPI()
setAndSaveDisplayedSites(fetchedSites)
return
}
const parsedLocalSites = JSON.parse(localSites)
setDisplayedSites(parsedLocalSites)
})()
}, [])
also checkout this hook that takes care of some things for you: https://usehooks.com/useLocalStorage/
Use the useContext hook for this purpose OR if you really just want to use the local storage anyhow, then use it but manage different states/variables for that.
Your current state (that you want to render on the screen)
Your fetched data (the one that you want to keep)
Hope this makes sense. Thankyou!

Categories