Fetch items before render component - reactjs

I have code like this below and my question is what can I do to get the products fetched before the components are rendered? I mean in my Panel component I have a .map function which shows an error "Cannot read property 'map' of undefined" because products were not fetched quickly enough.
const Announcments = () => {
const [announcements, setAnnouncement] = useState([]);
useEffect(() => {
const getProducts = async () => {
const res = await fetch("http://localhost:8000/api/products", {
method: "GET",
});
const data = await res.json();
await setAnnouncement(data);
};
getProducts();
}, []);
return (
<div className="announcments">
<TopNav results={announcements.length} />
<Panel announcements={announcements} />
<div className="down-pages-list">
<PagesList />
</div>
</div>
);
};
.map function:
{announcements.results.map((announcement) => (
<SingleAnnoun
price={announcement.price}
title={announcement.name}
photo={getPhoto(announcement.images[0].file_name)}
/>
))}

Issue
The issue here isn't that "products were not fetched quickly enough" but rather that your initial state doesn't match what you are attempting to render on the initial render. I.e. announcements is initially an empty array ([]) but you are attempting to map announcements.results which is obviously undefined.
Solution
Use valid initial state so there is something to initially render.
const [announcements, setAnnouncement] = useState({ results: [] });
Now announcements.results is defined and mappable.
This also assumes, of course, that your setAnnouncement(data); state update is also correct. i.e. that your JSON data is an object with a results property that is an array.
BTW, setAnnouncement is a synchronous function so there is nothing to await for.

Related

React dynamically added components not rendered

I'm dynamically adding instances of a custom (Kendo-React) component into an array in my main App.
The component:
const PersonDD = () => {
const ages = ["Child", "Adult", "Senior"];
return (
<div>
<div>Person:</div>
<DropDownList
data={ages} style={{ width: "300px", }}
/>
</div>
);
};
I'm adding one instance on initial render, and another two instances after the result from an Ajax call returns.
const SourceTab = (SourceTabProps) => {
....
var componentList = [];
componentList.push(<PersonDD/>);
async function getStrata(){
var url = '/access/.im.read';
const res = await axios.get( url );
console.log(res.data.item);
componentList.push(<PersonDD/>);
componentList.push(<PersonDD/>);
}
React.useEffect(() =>{
getStrata();
},[]);
return (
<Title title="People" />
<div className='assignment_div_css'>
{componentList}
</div>);
};
The problem I have is that the one instance in the initial array are rendered, but the two created after the Ajax call are not rendered.
Do I need to call .render() or something similar to refresh?
You can simply use react useState to rerender component and in jsx map them.
like this :
const SourceTab = (SourceTabProps) => {
const [componentList,setComponentList] = useState([PersonDD])
async function getStrata(){
var url = '/access/.im.read';
const res = await axios.get( url );
console.log(res.data.item);
setComponentList([...componentList,PersonDD,PersonDD])
}
React.useEffect(() =>{
getStrata();
},[]);
return (
<Title title="People" />
<div className='assignment_div_css'>
{componentList.map((Component,index)=> <Component key={index} />)}
</div>);
};
You need to remember that React only re-renders (refreshes the UI/view) when a state changes. Your componentList is not a state at the moment but just an ordinary variable. make it a state by using useState hook.
Not sure if it is a bad practice or not but I haven't seen any react project that keeps an entire component as a state so instead of creating a state with an array of components, just push a data representation of the components you want to render. Then display the component list using your list and using .map
Here's how it would look like.
....
const [personList, setPersonList] = useState([1]);
async function getStrata(){
var url = '/access/.im.read';
const res = await axios.get( url );
setPersonList(state => state.push(2)); //you can make this dynamic so it can rerender as much components as you like, for now im pushing only #2
}
React.useEffect(() =>{
getStrata();
},[]);
return (
<Title title="People" />
<div className='assignment_div_css'>
{personList.map((item, key) => <PersonDD key={key} />)}
</div>);
};
Need to use the map to render a list
<div className='assignment_div_css'>
{componentList.map(component => <>{component}</>)}
</div>);
also, use a usestate to variable
const [componentList , setComponentList ]= React.useState[<PersonDD/>];
inside function set like this
console.log(res.data.item);
setComponentList(state => [...state, <PersonDD/>, <PersonDD/>]);

Firebase call inside useEffect is not returning data properly

I have a component Photo.js responsible for making a call to to my firestore and rendering the returned data. The returned data is set to a state variable venues.
This data is then mapped over and rendered to the browser, however I'm getting the following error in the browser:
Cannot read properties of null (reading 'map')
And when I console log the state variable venues, it's being returned as null.
If I comment out the code responsible for mapping out the returned data (below), my webpage renders without problem - and if I uncomment the same code and save, the firebase call works and the data is rendered:
{venues.map((item) => {
return(<img src = {item.photoUrl}/>)
})}
Here's the Photos component controlling the firebase call:
import { useState,useEffect } from 'react'
import {getVenues} from '../../services/firebase.js'
const Photo = () => {
const [ venues,setVenues ] = useState(null)
useEffect(() => {
console.log('it got here')
async function getAllVenues(){
const response = await getVenues()
await setVenues(response)
}
getAllVenues()
},[])
console.log(venues)
return(
<div className = 'venueCard-container'>
{venues.map((item) => {
return(<img src = {item.photoUrl}/>)
})}
</div>
)
}
export default Photo
...and the the firebase functions in services/firebase.jss
import {firebase} from '../firebaseConfig'
export async function getVenues() {
const response = await firebase
.firestore()
.collection('venues')
.get()
return response.docs
.map((venue) => ({...venue.data()}))
}
I'm thinking this is some sort of async problem - the component is rendering before the firebase call has returned the data. Suggestions?
const [ venues,setVenues ] = useState(null)
You've set the initial value of the state to be null, so that's what it will be on the first render. Some time later the data will finish loading and you'll render again, but until that time, your component needs to work with the initial state. You could check for null and render nothing:
return(
<div className = 'venueCard-container'>
{venues && venues.map((item) => {
return(<img src = {item.photoUrl}/>)
})}
</div>
)
...or you could render a placeholder:
if (!venues) {
return <div>Loading...</div>
} else {
return (
<div className = 'venueCard-container'>
{venues.map((item) => {
return(<img src = {item.photoUrl}/>)
})}
</div>
)
);
}
...or you could make the initial state be an empty array, which means it will always have a .map method even before loading has finished:
const [ venues,setVenues ] = useState([])

react, map component, unexpected result

I am building Weather App, my idea is to save city name in database/localhost, place cities in useState(right now it's hard coded), iterate using map in first child component and display in second child component.
The problem is that 2nd child component outputs only one element (event though console.log prints both)
BTW when I change code in my editor and save, then another 'li' element appears
main component
const App = () => {
const [cities, setCities] = useState(['London', 'Berlin']);
return (
<div>
<DisplayWeather displayWeather={cities}/>
</div>
)
}
export default App
first child component
const DisplayWeather = ({displayWeather}) => {
const [fetchData, setFetchData] = useState([]);
const apiKey = '4c97ef52cb86a6fa1cff027ac4a37671';
useEffect(() => {
displayWeather.map(async city=>{
const res =await fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`)
const data = await res.json();
setFetchData([...fetchData , data]);
})
}, [])
return (
<>
{fetchData.map(data=>(
<ul>
<Weather
data={data}/>
</ul>
))}
</>
)
}
export default DisplayWeather
second child component
const Weather = ({data}) => {
console.log(data) // it prints correctly both data
return (
<li>
{data.name} //display only one data
</li>
)
}
export default Weather
The Problem
The setFetchData hooks setter method is asynchronous by default, it doesn't give you the updated value of the state immediately after it is set.
When the weather result for the second city is returned and set to state, the current value fetchData at the time is still an empty array, so you're essentially spreading an empty array with the second weather result
Solution
Pass a callback to your setFetchData and get the current previous value of the state and then continue with your spread accordingly.
Like this 👇🏽
setFetchData((previousData) => [...previousData, data]);

React Component is rendering twice

I have no idea why, the first render shows an empty object and the second shows my data:
function RecipeList(props) {
return (
<div>
{console.log(props.recipes)}
{/*{props.recipes.hits.map(r => (*/}
{/* <Recipe initial="lb" title={r.recipe.label} date={'1 Hour Ago'}/>*/}
</div>
)
}
const RECIPES_URL = 'http://cors-anywhere.herokuapp.com/http://test-es.edamam.com/search?i?app_id=426&q=chicken&to=10'
export default function App() {
const classes = useStyles();
const [data, setData] = useState({});
useEffect(() => {
axios.get(RECIPES_URL)
.then(res => {
setData(res.data);
})
.catch(err => {
console.log(err)
})
}, []);
return (
<div className={classes.root}>
<NavBar/>
<RecipeList recipes={data}/>
<Footer/>
</div>
);
}
I don't know why and I have struggled here for over an hour (React newbie), so I must be missing something.
This is the expected behavior. The reason you see two console logs is because, the first time RecipeList is called with no data (empty object), and the second time when the data becomes available. If you would like to render it only when the data is available you could do something like {Object.keys(data).length > 0 && <RecipeList recipes={data}/>}. By the way this is called conditional rendering.
This is perfectly normal, React will render your component first with no data. Then when your axios.get returns and update data, it will be rendered again with the new data

What's the React best practice for getting data that will be used for page render?

I need to get data that will be used for the page that I'm rendering. I'm currently getting the data in a useEffect hook. I don't think all the data has been loaded before the data is being used in the render. It's giving me an error "property lastName of undefined" when I try to use it in the Chip label.
I'm not sure where or how I should be handling the collection of the data since it's going to be used all throughout the page being rendered. Should I collect the data outside the App function?
const App = (props) => {
const [teams] = useState(["3800", "0200", "0325", "0610", "0750", "0810"]);
const [players, setPlayers] = useState([]);
useEffect(() => {
teams.forEach(teamId => {
axios.defaults.headers.common['Authorization'] = authKey;
axios.get(endPoints.roster + teamId)
.then((response) => {
let teamPlayers = response.data.teamPlayers;
teamPlayers.forEach(newPlayer => {
setPlayers(players => [...players, newPlayer]);
})
})
.catch((error) => {
console.log(error);
})
});
}, []);
let numPlayersNode =
<Chip
variant="outlined"
size="small"
label={players[1].lastName}
/>
return (...
You iterate over a teamPlayers array and add them one at a time, updating state each time, but players is always the same so you don't actually add them to state other than the last newPlayer.
Convert
teamPlayers.forEach(newPlayer => {
setPlayers(players => [...players, newPlayer]);
});
to
setPlayers(prevPlayers => [...prevPlayers, ...teamPlayers]);
Adds all new players to the previous list of players using a functional state update.
You also have an initial state of an empty array ([]), so on the first render you won't have any data to access. You can use a truthy check (or guard pattern) to protect against access ... of undefined... errors.
let numPlayersNode =
players[1] ? <Chip
variant="outlined"
size="small"
label={players[1].lastName}
/> : null
You should always create a null check or loading before rendering stuff. because initially that key does not exists. For example
<Chip
variant="outlined"
size="small"
label={players.length > 0 && players[1].lastName}
/>
this is an example of a null check
For loading create a loading state.
When functional component is rendered first, useEffect is executed only after function is returned.
and then, if the state is changed inside of useEffect1, the component will be rendered again. Here is a example
import React, {useEffect, useState} from 'react'
const A = () => {
const [list, setList] = useState([]);
useEffect(() => {
console.log('useEffect');
setList([{a : 1}, {a : 2}]);
}, []);
return (() => {
console.log('return')
return (
<div>
{list[0]?.a}
</div>
)
})()
}
export default A;
if this component is rendered, what happen on the console?
As you can see, the component is rendered before the state is initialized.
In your case, error is happened because players[1] is undefined at first render.
the simple way to fix error, just add null check or optional chaining like players[1]?.lastName.

Resources