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

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>
)
}

Related

Why do my ReactJS changes disappear on refreshing the page

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.

React hook renders [object object]

I am new in React and I am not sure what I do wrong. I am trying to use useEffect and save list in useState. But I am getting [object object] back.
I am building simple weather app and I managed to save 1 result into useState, but now I wanna have 3 constant cities showing weather on page load, not depending on what user enteres. Here is the code
const [query, setQuery] = useState('');
const [weather, setWeather] = useState({});
const [weatherConst, setWeatherConst] = useState([]); <-- not working
useEffect(() => {
fetch(`${api.base}group?id=3413829,6618983,2759794&units=metric&APPID=${api.key}`)
.then(res => res.json())
.then(result => {
setWeatherConst(result)
console.log("new list" + result)})
}, []) <-- not working
function apiCall() {
fetch(`${api.base}weather?q=${query}&unit=metric&APPID=${api.key}`)
.then(res => res.json())
.then(result => {
setWeather(result)
setQuery('')
console.log(result)
})
}
When I console log "new list" i get [object object], but when I run link by itself in browser I get list of 3 cities back
Image of result getting back
Here is a quick snippet illustrating the sequence.
We set the initial state to [] as you have.
On the first render we check state.length and because our initial array is empty it renders <h2>Loading...</h2>.
The useEffect runs and waits 1 second before callingsetState([...loadedState]) which triggers a render (the useEffect cleanup runs here and clears the timer).
We check state.length again and now because our array is no longer empty we render state[0].name, state[1].name, state[2].name. (For a known index or limited number of indexes this is ok, but you'll usually want to use state.map())
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
const { useState, useEffect } = React
const loadedState = [{name: 'foo'},{name: 'bar'},{name: 'baz'}]
function App() {
const [state, setState] = useState([])
useEffect(() => {
const timer = setTimeout(() => {
setState([...loadedState]);
}, 1000);
return () => clearTimeout(timer);
},[])
return (
<div className="container">
{state.length ?
(<div>
<p>{state[0].name}</p>
<p>{state[1].name}</p>
<p>{state[2].name}</p>
</div>) : (<h2>Loading...</h2>)
}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
</script>
<div id="root"></div>
Note
The answer to your actual question was given in the comments by jonrsharpe.
"new list" + result is string concatenation, which will implicitly call result.toString(), which gives "[object Object]". Use console.log("new list", result) instead, or if you want to make a more readable string form try e.g. JSON.stringify(result, null, 2).
If you would like another example of explanation:
I would like to explain by simple words. When we use "useEffect" or "setSmth (created by useState hook)" hook we need to understand that this is not a simple function which we just call. This is some kind of async function which calls when it needs.
So, for example :
useEffect(() => {
axios
.get(
"https://api.oceandrivers.com:443/v1.0/getForecastPoints/cnarenal/language/en"
)
.then((res) => res.data.data)
.then((res) => {
setWeather(res);
console.log(weather);
})
.catch((e) => {
console.table(e);
});
}, []);
We can not see results in "console.log(weather)" , because setWeather(res) didn't finish work. We see result only after render of component. So, if we create a list:
let weatherList = weather.map((el) => {
return <li key={el.name}> {el.name} </li>;
});
return (
<div className="App">
<ul>{weatherList}</ul>
</div>
);
we'll see all information that we need.
If you would like to see all code: https://codesandbox.io/s/wonderful-feynman-n0h39?file=/src/App.js:503-677
Sorry for my English. If I said smth wrong tell me please, I'll be very appreciated!

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.

Unable to display promise data inside a method in render

I realize I can't return objects in a react render but I'm having a problem figuring out how to do this.
Error message:
Invariant violation, objects are not valid as a React child error
My method:
displayListItemComponent = item => {
return this.getConcernsDB(item.values)
.then(returnedConcerns => {
if(returnedConcerns.length) {
returnedConcerns.forEach(el => {
return(el.name);
})
}
}
)
}
Partial render:
<CollapseBody>
{ el.details.map(item => (
this.displayListItemComponent(item)
))}
</CollapseBody>
Here I am trying to return el.name from displayListItemComponent
I am attempting to return displayListItemComponent in my react render.
My assumption was that by returning the function this.getConcernsDB that it would chain down and it would return el.name. How can I achieve this?
Your method returns a Promise. Render should be synchronous and have no side effects (e.g. it should only return allowed values from props and state). Therefore you need to store your data in state, and render elements from state
You could do something like this. Also what does el.name contain?
displayListItemComponent =(props) => {
const {item} = props;
const [data, setData] = useState();
useEffect(() => {
// i see you did this.getConcernsDB. where is it coming from?
getConcernsDB(item.values)
.then(returnedConcerns => {
setData(returnedConcerns.values(el => el.name))
)
}, [])
if(!data) return <SomeLoadingSpinner />
// now you have access to the names in `data` variable. Return whatever component you want using the `data`
}

useLoopCallback -- useCallback hook for components created inside a loop

I'd like to start a discussion on the recommended approach for creating callbacks that take in a parameter from a component created inside a loop.
For example, if I'm populating a list of items that will have a "Delete" button, I want the "onDeleteItem" callback to know the index of the item to delete. So something like this:
const onDeleteItem = useCallback(index => () => {
setList(list.slice(0, index).concat(list.slice(index + 1)));
}, [list]);
return (
<div>
{list.map((item, index) =>
<div>
<span>{item}</span>
<button type="button" onClick={onDeleteItem(index)}>Delete</button>
</div>
)}
</div>
);
But the problem with this is that onDeleteItem will always return a new function to the onClick handler, causing the button to be re-rendered, even when the list hasn't changed. So it defeats the purpose of useCallback.
I came up with my own hook, which I called useLoopCallback, that solves the problem by memoizing the main callback along with a Map of loop params to their own callback:
import React, {useCallback, useMemo} from "react";
export function useLoopCallback(code, dependencies) {
const callback = useCallback(code, dependencies);
const loopCallbacks = useMemo(() => ({map: new Map(), callback}), [callback]);
return useCallback(loopParam => {
let loopCallback = loopCallbacks.map.get(loopParam);
if (!loopCallback) {
loopCallback = (...otherParams) => loopCallbacks.callback(loopParam, ...otherParams);
loopCallbacks.map.set(loopParam, loopCallback);
}
return loopCallback;
}, [callback]);
}
So now the above handler looks like this:
const onDeleteItem = useLoopCallback(index => {
setList(list.slice(0, index).concat(list.slice(index + 1)));
}, [list]);
This works fine but now I'm wondering if this extra logic is really making things faster or just adding unnecessary overhead. Can anyone please provide some insight?
EDIT:
An alternative to the above is to wrap the list items inside their own component. So something like this:
function ListItem({key, item, onDeleteItem}) {
const onDelete = useCallback(() => {
onDeleteItem(key);
}, [onDeleteItem, key]);
return (
<div>
<span>{item}</span>
<button type="button" onClick={onDelete}>Delete</button>
</div>
);
}
export default function List(...) {
...
const onDeleteItem = useCallback(index => {
setList(list.slice(0, index).concat(list.slice(index + 1)));
}, [list]);
return (
<div>
{list.map((item, index) =>
<ListItem key={index} item={item} onDeleteItem={onDeleteItem} />
)}
</div>
);
}
Performance optimizations always come with a cost. Sometimes this cost is lower than the operation to be optimized, sometimes is higher. useCallback it's a hook very similar to useMemo, actually you can think of it as a specialization of useMemo that can only be used in functions. For example, the bellow statements are equivalents
const callback = value => value * 2
const memoizedCb = useCallback(callback, [])
const memoizedWithUseMemo = useMemo(() => callback, [])
So for now on every assertion about useCallback can be applied to useMemo.
The gist of memoization is to keep copies of old values to return in the event we get the same dependencies, this can be great when you have something that is expensive to compute. Take a look at the following code
const Component = ({ items }) =>{
const array = items.map(x => x*2)
}
Uppon every render the const array will be created as a result of a map performed in items. So you can feel tempted to do the following
const Component = ({ items }) =>{
const array = useMemo(() => items.map(x => x*2), [items])
}
Now items.map(x => x*2) will only be executed when items change, but is it worth? The short answer is no. The performance gained by doing this is trivial and sometimes will be more expensive to use memoization than just execute the function each render. Both hooks(useCallback and useMemo) are useful in two distinct use cases:
Referencial equality
When you need to ensure that a reference type will not trigger a re render just for failing a shallow comparison
Computationally expensive operations(only useMemo)
Something like this
const serializedValue = {item: props.item.map(x => ({...x, override: x ? y : z}))}
Now you have a reason to memoized the operation and lazily retrieve the serializedValue everytime props.item changes:
const serializedValue = useMemo(() => ({item: props.item.map(x => ({...x, override: x ? y : z}))}), [props.item])
Any other use case is almost always worth to just re compute all values again, React it's pretty efficient and aditional renders almost never cause performance issues. Keep in mind that sometimes your efforts to optimize your code can go the other way and generate a lot of extra/unecessary code, that won't generate so much benefits (sometimes will only cause more problems).
The List component manages it's own state (list) the delete functions depends on this list being available in it's closure. So when the list changes the delete function must change.
With redux this would not be a problem because deleting items would be accomplished by dispatching an action and will be changed by a reducer that is always the same function.
React happens to have a useReducer hook that you can use:
import React, { useMemo, useReducer, memo } from 'react';
const Item = props => {
//calling remove will dispatch {type:'REMOVE', payload:{id}}
//no arguments are needed
const { remove } = props;
console.log('component render', props);
return (
<div>
<div>{JSON.stringify(props)}</div>
<div>
<button onClick={remove}>REMOVE</button>
</div>
</div>
);
};
//wrap in React.memo so when props don't change
// the ItemContainer will not re render (pure component)
const ItemContainer = memo(props => {
console.log('in the item container');
//dispatch passed by parent use it to dispatch an action
const { dispatch, id } = props;
const remove = () =>
dispatch({
type: 'REMOVE',
payload: { id },
});
return <Item {...props} remove={remove} />;
});
const initialState = [{ id: 1 }, { id: 2 }, { id: 3 }];
//Reducer is static it doesn't need list to be in it's
// scope through closure
const reducer = (state, action) => {
if (action.type === 'REMOVE') {
//remove the id from the list
return state.filter(
item => item.id !== action.payload.id
);
}
return state;
};
export default () => {
//initialize state and reducer
const [list, dispatch] = useReducer(
reducer,
initialState
);
console.log('parent render', list);
return (
<div>
{list.map(({ id }) => (
<ItemContainer
key={id}
id={id}
dispatch={dispatch}
/>
))}
</div>
);
};

Resources