React not getting API results on first render - javascript

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.

Related

Looping/Map/Iterating over object results in Cannot read properties of undefined (reading 'map') error, but data is present

I have a react component that gets data from an API end point. For the purposes of debugging when I call the API I log the result of the API call to the console and it looks like this:
The problem I have is that when I try to render the BreadCrumbLinks Property in my component I get this error:
TypeError: Cannot read properties of undefined (reading 'map')
at BreadCrumbHeader
I am getting an "Undefined" error, but I know that the data is present because I can read and render all of the other fields (for example BreadCrumbBgImage).
Also, if I comment out the map/loop the rest of the data is displayed correctly when the page loads. Then if I uncomment it out and save the file the data for the map/loop now shows correctly.
I can only assume that the code is trying to render the contents of the loop before it has been loaded.
This is what the code for the component looks like:
import React, { useState, useEffect } from 'react';
import API from "../../API";
import { useLocation } from 'react-router-dom';
import { BreadCrumbTitleSection, SubtitleSection, Subtitle } from './breadCrumbHeaderStyle';
import { Breadcrumb } from 'react-bootstrap';
function BreadCrumbHeader() {
const location = useLocation();
const [breadCrumbData, setBreadCrumbData] = useState([]);
const getBreadCrumbData = async () => {
const breadCrumbHeaderResponse = await API.fetchBreadCrumbHeader(location.pathname);
setBreadCrumbData(breadCrumbHeaderResponse);
console.log("OUT-PUT-OF-API-CALL");
console.log(breadCrumbHeaderResponse);
console.log("END-OF-OUT");
};
useEffect(() => {
getBreadCrumbData();
}, [location.pathname]);
return (
<div>
<BreadCrumbTitleSection backgroundUrl={breadCrumbData.BreadCrumbBgImage}>
<div className="container">
<div className="row no-gutters">
<div className="col-xs-12 col-xl-preffix-1 col-xl-11">
<h1 className="h3 text-white">{breadCrumbData.BreadCrumbTitle}</h1>
<Breadcrumb>
{breadCrumbData.BreadCrumbLinks.map(breadCrumbLink => (
<Breadcrumb.Item href={breadCrumbLink.LinkUrl} key={breadCrumbLink.Id} active={breadCrumbLink.IsActive}>
{breadCrumbLink.LinkText}
</Breadcrumb.Item>
))}
</Breadcrumb>
</div>
</div>
</div>
</BreadCrumbTitleSection>
<SubtitleSection>
<Subtitle> {breadCrumbData.SubTitle}</Subtitle>
</SubtitleSection>
</div>
);
}
export default BreadCrumbHeader;
Can anyone explain what is going on here and how I can solve i?
You are trying to map data before its fetched, so its an empty array (initial value of breadCrumbData state). You should use optional chaining:
{breadCrumbData?.BreadCrumbLinks?.map(breadCrumbLink =>
You are tryng to map your array before the state change, the useEffect is called on first render, your array don't have the state in the first render, you can use something like a loading hook, like this
const [loading, setLoading] = useState(false)
useEffect(() =>{
setLoading(true)
fetchData()
},[])
const fetchData = () =>{
//my api call
setLoading(false)
}
return (
{loading ? (
// my loading message or function
): (
// my show component
)}
)
this is a just an litle example how you can do

ReactJS Update child component after API call returns

So I have a simple reactJS app that contains a app.js with a HomePage.js
The HomePage.js need to display some data for me that I fetch trough an await API.get method from amplify. ( : https://docs.amplify.aws/lib/restapi/fetch/q/platform/js/ )
The API.get method triggers the .then response and then gives me exactly the correct array with item that I want.
Now I want to render those items on my HomePage.js, which is a functional component.
My app.js looks like this (ignore the token stuff, this is another problem i need to solve) :
import React, { useState } from "react";
import logo from './logo.svg';
import './App.css';
import Amplify, { API, Auth } from 'aws-amplify';
import { withAuthenticator , Link } from "#aws-amplify/ui-react";
import '#aws-amplify/ui-react/styles.css';
import { Button } from '#aws-amplify/ui-react';
import Appbar from "../src/components/Appbar/Appbar";
import Homepage from './components/Homepage/Homepage';
// let user;
let user = {};
let userName = "placeholder";
// let pageLoaded = false;
let passArray;
let zorgSearchData;
function App() {
callApi();
async function callApi() {
user = await Auth.currentAuthenticatedUser()
const token = user.signInUserSession.idToken.jwtToken
userName = user.username;
const requestData = {
headers: {
Authorization: token
}
}
zorgSearchData = await API
.get('caresyncapi' , '/items/zorgsearch/' + userName)
.then(response => {
console.log("resp!")
console.log(response);
passArray = response;
})
}
return (
<div className="App">
<Appbar/>
{<Homepage passedArray={passArray} />}
</div>
);
}
export default withAuthenticator(App);
The API.get calls and the .then prints out the exact response I want. I pass passArray as a prop to HomePage.js which (i think and am probably wrong) should update with the new data. The problem is when the .then is called there is no change in the Homepage.js . The props I retrieve in Homepage.js is always undefined. I think this is because the API is async and retrieves the prop after I pass it for the first time. But how do I update this? Anyway, my Homepage.js looks like this:
import React, { useState } from 'react';
import "./homepage.css";
import { makeStyles } from '#material-ui/core/styles';
import InputLabel from '#material-ui/core/InputLabel';
import MenuItem from '#material-ui/core/MenuItem';
import FormControl from '#material-ui/core/FormControl';
import Select from '#material-ui/core/Select';
import { Typography } from '#material-ui/core';
const useStyles = makeStyles((theme) => ({
formControl: {
margin: theme.spacing(1),
minWidth: 120,
},
selectEmpty: {
marginTop: theme.spacing(2),
},
}));
const Homepage = ({props}) =>{
const classes = useStyles();
const [age, setAge] = useState('');
console.log(props);
function handleChange (event) {
console.log(event.target.value);
// console.log({passedData})
//setAge(event.target.value);
};
return (
<>
<Typography variant="h1">{`Welcome,username`}</Typography>
<FormControl variant="outlined" className={classes.formControl}>
<InputLabel id="demo-simple-select-outlined-label">Age</InputLabel>
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
value={age}
onChange={handleChange}
label="Patienten"
>
{/* {this.state.searchData.map(x=> <MenuItem >{x.PatientNaam}</MenuItem>)} */}
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
<div className="homepage-container">
<div className="homepage-text-container">
<h1 className="welcome-message">Hello, {props}</h1>
<p>lorem ipsum</p>
</div>
<div className="chart-container">
<div className="chart-1">
<h3>Chart 1</h3>
<div className="chart"></div>
<p>More text....</p>
<p></p>
</div>
</div>
</div>
</>
)
}
export default Homepage
As you can see I am also trying to fill the select form with my array names, but I think I can figure that out as long as I have a valid array. I tried different methods of the handleChange and passing props, but nothing seems to work.
TL;DR : I have a app.js which calls an async get function, this returns a valid object I use. The problem is I pass this object array to Homepage.js ( {} ) before the async method returns a value. So the array is always undefined in Homepage.js
Can someone point me in the right way? Do I need to force a change? Is there a way to pass the prop again? Is there something I can do in the handleChange method? I can access the .then method, do I need to do something from there? Simply updating the variable doesn't seem enough.
Thanks in advance! Sorry for the ramble im new to reactJS and I am trying to solve this problem after a few beers.

How to refetch queries from sibling component with react-query

I was wondering how to refetch a query from another sibling component with react-query.
Lets say I have Component A
import {useQuery} from "react-query";
function ComponentA(){
const getData = async () => data //returns api data
const {data, refetch} = useQuery(["queryA"], async () => await getData())
return(
<div>
{data}
</div>
)}
And in Component B
import {useQuery, QueryClient} from "react-query";
function ComponentB(){
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
},
},
})
const refreshQueryFromComponentA = async () => {
await queryClient.refetchQueries(["queryA"], { active: true })
}
return(
<div>
<button onClick={refreshQueryFromComponentA}/>
</div>
)}
And in Page.js
import ComponentA from "./componentA";
import ComponentB from "./componentB";
function Page(){
return(
<div>
<ComponentA/>
<ComponentB/>
</div>
)}
When I call the refreshQueryFromComponentA function in ComponentB I do not see the query refresh in ComponentA or a new call to the backend in the network tab. I also use the correct query-key but I am only able to refetch the query in ComponentA with the refetch() function which comes from the useQuery function.
I think it's possible what I'm trying to do, since react-query uses context and should be available throughout the whole application. But maybe I'm using the wrong method or misinterpreted it.
Thanks in advance everyone!
There needs to be one QueryClient at the top of your app. The QueryClient holds the queryCache, which stores your data. If you create a new one in componentB, it won’t share anything with componentA. Also, make sure to add it to the QueryClientProvider and retrieve it via useQueryClient(). The client also needs to be stable, so don’t create a new one each render. This is from the first page of the docs:
import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
2
3 const queryClient = new QueryClient()
4
5 export default function App() {
6 return (
7 <QueryClientProvider client={queryClient}>
8 <Example />
9 </QueryClientProvider>
10 )
11 }

Hacker Rank clone in React

I'm making a Hacker rank clone project in React, and so far I tried to get all the New Posts from the API.
Since the API only gives me id's I was just able to map over the piece of state that holds that information. But now I want to get the whole data from every id that I got , and then display all the posts. It's been really confusing for me, and i really need some help. Well, to resume everything: I got the id's from a api call and stored it in my state. Now I want to get all of the id's and make another request, but this time I'll get the info based on that specific Id. Here's the code:
import React, { useState } from "react";
import "./styles.css";
import TopList from "./components/TopList";
export default function App() {
const [state, setState] = useState({
data: [23251319, 23251742, 23251158],
results: []
});
const fetcher = id => {
fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json?print=pretty`)
.then(res => res.json())
.then(data => {
console.log(data);
setState({
results: data
});
});
};
return (
<div>
<TopList data={state.data} fetcher={fetcher} />
</div>
);
}
import React from "react";
import Top from "./Top";
function TopList({ data, fetcher }) {
const mapped = data.map(item => (
<Top fetcher={fetcher} id={item} key={item} />
));
return <div>{mapped}</div>;
}
export default TopList;
import React from "react";
function Top({ id, fetcher }) {
fetcher(id);
return (
<div>
<h1>Hello from top</h1>
</div>
);
}
export default Top;
As I told you in the comments, the fetcher() function already gets the data of each item using the IDs you have from the first request. I think that a good place to call this function is the TopStoryComponent, as there will be an instance of this component for each ID in the list.
import React from "react";
function TopStoryComponent({ identification, fetcher }) {
// this will print the data to the console
fetcher(identification);
return <div>{identification}</div>;
}
export default TopStoryComponent;
Let me know if it helps you get what you need!

React Context API & Lifecycle methods - How to use together

I'm struggling to understand how to proceed with a small React app I am making.
I have a budget tracker, where you can add costs (mortgage, bills etc.) and they have a cost value. Each time you add, edit or delete one of these, I want the global state to change, which is stored in a context.
I basically have a 'remaining balance' value, that I want to recalculate each time something changes.
I figured I'd use a life cycle method or useEffect, but when I use that in my App.js (so that it watches for changes in all subcomponents), I can't get it to work, because the life cycle method is calling a method from my Context, but because it's not wrapped in the provider, it can't access the method in the Context.
Is this a common problem and is there are recommended way to fix it? I can't seem to find a similar problem on the GoOgLe.
App.js:
import React, { useState, useContext, useEffect } from "react";
import "./css/main.css";
import Header from "./layout/Header";
import BudgetInfo from "./components/BudgetInfo";
import PaymentForm from "./components/PaymentForm";
import CostToolbar from "./components/CostToolbar";
import Costs from "./components/Costs";
import BudgetContext from "./context/budgetContext";
import BudgetState from "./context/BudgetState";
const App = () => {
const budgetContext = useContext(BudgetContext);
const { updateBalance } = budgetContext;
useEffect(() => {
updateBalance();
});
return (
<BudgetState>
<Header darkModeToggle={toggleDarkMode} />
<main
className={"main-content" + (darkMode.darkMode ? " dm-active" : "")}
>
<div className="wrap content-wrap">
<BudgetInfo />
<PaymentForm />
<CostToolbar />
<Costs />
</div>
</main>
</BudgetState>
);
};
export default App;
You need to wrap the App component. Try the simple example.
import React, { useEffect, useContext } from 'react';
import ThemeContext from './../context/context';
const Sample = () => {
const context = useContext(ThemeContext);
useEffect(() => {
console.log(context,'--')
},[])
return(
<ThemeContext.Consumer>
{color => (
<p style={{ color }}>
Hello World
</p>
)}
</ThemeContext.Consumer>
)
}
export default Sample;

Categories