Why do my ReactJS changes disappear on refreshing the page - reactjs

I'm new to React and I'm trying to render a list of Pokemons.
I'm fetching the pokemon names from a local file and then using those names to trigger HTTP calls to my backend server, to get the pokemon images. Here's my code so far:
function PokemonList(props) {
const [pokemonList, setPokemonList] = useState([]);
const [isFetched, setIsFetched] = useState(false);
const [renderedList, setRenderedList] = useState([]);
useEffect(() => {
fetch(raw)
.then((r) => r.text())
.then((text) => {
setPokemonList(text.split("\n"));
setIsFetched(true);
});
}, []);
// I believe this is to blame, but I don't know why. On refresh, pokemonList becomes empty
useEffect(() => {
setRenderedList(populateRenderedList(pokemonList));
}, []);
return !isFetched ? null : (
<div className="list">
{renderedList}
<PaginationBar listSize={renderedList.length} list={renderedList} />
</div>
);
}
function populateRenderedList(pokemonList) {
let pokemonAPI = "https://pokeapi.co/api/v2/pokemon-form/";
const temp = [];
console.log(pokemonList);
pokemonList.forEach((pokemonName) => {
let renderedPokemon = (
<a className="pokemonLink" href={pokemonAPI + pokemonName.toLowerCase()}>
<PokemonDiv name={pokemonName.toLowerCase()} />
<h3>{pokemonName}</h3>
</a>
);
temp.push(renderedPokemon);
});
return temp;
}
As I have commented on the code, the 'pokemonList' renders fine when I make any changes to the PokemonList function. But the moment I refresh my page, 'pokemonList' becomes empty. Why is that?
I previously was not using 'useState' to populate my 'renderedList' list. So I believe the problem is happening because I'm using 'useState' , but I don't know why that's happening.
I had tried making 'renderedList' not a state, but I had to, for I am thinking about passing it as props to another child component, in order to change it's state.

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([])

SetState inside UseEffect causing infinite loop

export const Upload = ({ initialfileList = [] }) => {
console.log("called...")
const [files,setFiles] = useState(initialfileList)
useEffect(() => {
setFiles(initialfileList)
}, [initialfileList])
return(
.....
)
}
I will not be sending initialfileList in intial render.
so I'm trying to update the state (files) when intialfileList value Changes.
But I'm not able to get why code is going into infinite loop.. Can someone help...
EDIT:
what I'm trying to achieve is there are two uploads in the form (say upload A and upload B) and checkbox as well. when the user uploads an image (say imgc) in Upload A and hits the checkbox, I wanted img c to be autouploaded in Upload B as well..
Ah, I see. How about this?
export const Upload = ({ initialfileList = [] }) => {
const [files, setFiles] = useState(initialfileList);
useEffect(() => {
function isDifferentFiles(originFiles, newFiles) {
return originFiles.length !== newFiles.length; // or whatever logic here.
}
if (isDifferentFiles(files, initialfileList)) {
setFiles(initialfileList);
}
}, [initialfileList]);
return <div>{files.length}</div>;
};
Btw, you might need to consider move the state to parent.
It sounds like you need to lift your state up - rather than storing fileLists locally, store a single fileList in the parent component, pass that down to both Upload A and Upload B, and also pass a setFileList function (which updates the state in the parent component).
i.e.:
// parent component
const Parent = () => {
const [fileList, setFileList] = useState([])
return (
<Upload fileList={fileList} setFileList={setFileList} />
<Upload fileList={fileList} setFileList={setFileList} />
)
}
const Upload = ({fileList, setFileList}) => {
return (
<UploadComponent files={fileList} onUpload={setFileList} />
)
}
This way, either upload component being updated will update both.

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.

why is useEffect not updating the value after the api call?

im calling an api that does a fetch , but when using setHotSalesArray, the hotSalesArray is empty after useEffect Finishes
i have tried calling another function sending the data, also tried putting the data inside a variable to try and update it that way
here is the part im having trouble with
const [isActive, setIsActive] = useState(true);
const [hotSalesArray, setHotSales] = useState({});
useEffect(()=>{
let infoData;
API.getToken().then(
(data) =>API.getHotSale(data).then(info => (
infoData = info,
setHotSales(infoData),
setIsActive(false),
console.log(hotSalesArray,info, infoData)
),
)
)
},[])
this is what i get on the console :
{} {hotsales: Array(1)} {hotsales: Array(1)}
and the result im expecting is this:
{hotsales: Array(1)} {hotsales: Array(1)} {hotsales: Array(1)}
Update:
so i learned that i need a re-render to happen so i can have my new value inside the variable, my problem is i have this in my return
<div className="hotSalesContainer">
<h1 className="activitiesLabelHot">
Ventas Calientes
</h1>
{hotSales}
{ hotSales !== undefined ? hotSales.map((hotsale,index) => <PropertyElement data={hotsales} key={index.toString()} /> ) : ""}
</div>```
so i need the object hotSales to have something before i return that, i used to do this call before with componentWillMount, but im trying to learn how to use hooks
When you do console.log(hotSalesArray, info, infoData), hotSalesArray will have the value of the current render, which is {} (the initial value). It won't be until a re-render occurs that hotSalesArray will have the value of { hotsales: [...] }, but you won't see that in the console because you're only logging when the response comes back from your API.
Add a console log directly in the render and you'll see.
I fixed the syntax errors and did some cleanup. Besides the syntax errors though, you had the right idea. This could be improved upon with useReducer perhaps, but I didn't want to overcomplicate the answer :)
const MyComponent = () => {
const [isActive, setIsActive] = useState(true);
const [hotSales, setHotSales] = useState({});
useEffect(
() => {
API.getToken()
.then(data => API.getHotSale(data))
.then(info => {
setHotSales(info);
setIsActive(false);
})
},
[]
);
const { hotsales = [] } = hotSales;
return (
<div className='hotSalesContainer'>
<h1 className='activitiesLabelHot'>Ventas Calientes</h1>
{ hotsales.length &&
hotsales.map((sale, index) =>
<PropertyElement data={sale} key={index} />
)
}
</div>
)
}

Resources