I have built an axios call that returns a Promise which holds a promiseResult with an array.
Now I want to show a popup based on if the array is empty or not.
this is my code:
createPopup = () => {
const { myUser } = this.props;
const { showPopup } = this.state;
MyAxiosCall.fetchGetArray(myUser.id)
.then((data) => {
if (data.length < 1) { this.setState({ showPopup: true }) }
if (data.length > 0) { this.setState({ showPopup: false }) }
});
if (!showPopup) {
return;
}
if (showPopup) {
return <Popup/>
}
}
Right now I am using a bool(ShowPopup that is set to false) to show or hide the popup. My problem is that when I debug the createPopup function it loops the createpopupfunction and always seem to start as false, but it gets the right state the second time it loops thru the function.
How do I get the function to stop looping?
And how can I get the function to wait for the statechange based from the response from my api?
It's been a while since i used react with classes.
in functional components you can simply use useEffect.
in this case setting showPopup as an attribute for <PopUp /> and managing visibility from inside <PopUp /> probably would solve you problem
It’s a lifecycle issue. Convert to a functional component and this should fix your issue.
I think this is happening because your conditions for popup are outside the then() block.
So what's happening is for the first time promise is in pending state and during this your state is unchanged because you are updating the state in then block. As soon as promise gets fullfilled your state changes and you are getting correct state in second go.
Try using async await so that js will wait until your api call is complete and then it will execute the conditions.
Related
Im making a function in react that get some information from Youtube API which I want it to get called just once when I refresh the page and put that information in a state. But when I use it in componentDidMount, it wont save the information and my state is still empty. here is the code:
constructor(props) {
super(props);
this.state = { vid: [] };
this.vidSearch = this.vidSearch.bind(this);
}
vidSearch = async () => {
const youtubeVid = await Youtube.get("/search");
this.setState({ vid: youtubeVid.data.items });
};
componentDidMount() {
this.vidSearch();
console.log(this.state.vid);
}```
setState may be asynchronous, so you won't be able to immediately check the state. But it has an optional callback which you can use that is called after the process has completed.
vidSearch = () => {
const youtubeVid = await Youtube.get("/search");
this.setState({ vid: youtubeVid.data.items }, () => {
console.log(this.state.vid);
});
};
componentDidMount() {
this.vidSearch();
}
According to the official documentation:
You may call setState() immediately in componentDidMount(). It will
trigger an extra rendering, but it will happen before the browser
updates the screen. This guarantees that even though the render() will
be called twice in this case, the user won’t see the intermediate
state. Use this pattern with caution because it often causes
performance issues. In most cases, you should be able to assign the
initial state in the constructor() instead. It can, however, be
necessary for cases like modals and tooltips when you need to measure
a DOM node before rendering something that depends on its size or
position.
In this case I would change my code to this:
constructor(props) {
this.state = {
vid: Youtube.get('/search').data.items
}
}
vidSearch is async and you are not awaiting the returned promise in componentDidMount before calling console.log.
All async functions wrap the return value in a promise, even implicit returns.
You can try this.vidSearch().then(() => console.log(this.state)).
This question already has an answer here:
How to get value from another function inside react native flatList?
(1 answer)
Closed 2 years ago.
**I am trying to filter out posts from blocked users. **
I made an async function to check if a user is blocked or not:
checkBlocked = async userId => {
try {
let snapshot = await firebase.database().ref('Blocked/' + firebase.auth().currentUser.uid).orderByChild('uid').equalTo(userId).once('value')
return snapshot.exists();
}
catch(error) {
return false;
}
}
I want to return the value inside flatlist so that accordingly the post shows up there:-
<FlatList
inverted
extraData={this.state.blocked}
data={this.state.arrData}
keyExtractor={item => item.key}
renderItem={({ item }) => {
this.checkBlocked(item.id).then(val => this.setState({blocked: val}));
if(this.state.blocked == true){
return (
//something
)
}
else {
return (
//something
)
}
}}
The above approach is not working, where am I going wrong?
We don't have all the details exactlty, but I can see the logic flow here is strange. Let's look at what's happening
FlatList renders with this.state.arrData
On each iteration of renderItem, checkBlocked makes an async call with the id of a user
You render something based on this.state.blocked
Your checkBlocked promise resolves, and the blocked variable in state is overwritten with the returned value
You are making a promise (checkBlocked(id).then()), but your render does not wait for that promise to resolve before it returns something. So your this.state.blocked is not yet defined as what you think in your return statement. Also, setState is async, meaning you can't rely on the value in your return statements to be the value you just set from checkBlocked.
A better pattern would be to that before you render your component, run your userlist through an async loop, where each entry is checked with checkBlocked. You can then construct an array of objects, say usersWithBlockedStatus, each object with a user name and blocked status. Then you filter out all entries who's blocked status is true, and call it usersThatAreNotBlocked. Only once all this is complete, you render your FlatList with the data as usersThatAreNotBlocked.
You can find another well written explanation in the question How to call fetch() inside renderItem on FlatList?
I'm working with Material-UI components on the project and is using AutoComplete component in my app.
In the example from Material-UI team, I came across an interesting example of the AutoComplete Ajax data: https://material-ui.com/components/autocomplete/#asynchronous-requests
They are using React Hooks to fetch the data from the server:
React.useEffect(() => {
let active = true;
if (!loading) {
return undefined;
}
(async () => {
const response = await fetch('https://country.register.gov.uk/records.json?page-size=5000');
await sleep(1e3); // For demo purposes.
const countries = await response.json();
if (active) {
setOptions(Object.keys(countries).map((key) => countries[key].item[0]));
}
})();
return () => {
active = false;
};
}, [loading]);
Why do we use active variable here? Why we return a function that changes this variable to false? It is always true in the if-statement.
Thanks in advance for the answer
The function returned from useEffect is a cleanup function. It is called when the component un-mounts - and is usually used to unsubscribe to events, cancel pending promises etc that were used in the useEffect.
The active variable is used make sure that you aren't updating the state on something that doesn't exist anymore. It's somewhat like the isMounted anti-pattern that existed in class components.
When you try to update state on an un-mounted component, React will throw a warning -
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application.
Having the active variable prevents that in the following way:
Your component loads
The useEffect calls an async fetch - this will take time
Now say, before the server from the response is returned, you navigate away from the page (or perform some other action that un-mounts the component)
That will cause the component to unmount and the cleanup function to be called:
return () => {
active = false;
};
active is now set to false
Finally, we get our response from the server. And now, it'll encounter the false active value, and not update the state.
// active === false,
// this will skip `setOptions`
if (active) {
setOptions(...);
}
This is a pattern which is used to avoid two situations:
updating the state if component containing this hook has already unmounted before HTTP request could complete.
avoid race conditions
Function returned by callback function of useEffect hook is used to perform the clean up, like componentWillUnmount in class based components. This cleanup function runs when the component unmounts or before running the effect next time.
So if component is unmounted when the HTTP request was in progress, cleanup function of useEffect hook will run and will set active to false. After that, whenever the result of HTTP request returns, state won't be updated because active will be false.
See Effects with cleanup section of react docs to understand the cleanup function of useEffect hook and see Race Conditions to better understand the problem related to race conditions that is solved here by using active variable.
I need to get data from my SQLite database and then save it in my component's state. The database part is working fine, the problem is that it doesn't get saved to state quite quickly enough. If I add some artificial delay with setTimeout then it works fine. How could I better tie these things together so that it all works with the correct order and timing without a million callbacks?
This doesn't work:
let returnedData;
// This function works, the data comes back correctly eventually
returnedData = getDataFromDB();
this.setState({
dbData: returnedData //but returnedData is still empty at this point
})
// Data is not back in time to see in the variable or in state:
console.log(returnedData); // Undefined
console.log(this.state.dbData); // Undefined
But this does work:
let returnedData;
returnedData = getDataFromDB();
// If I add this tiny delay, it all works
setTimeout(function(){
this.setState({
dbData: returnedData
})
console.log(returnedData); // Shows correct data
console.log(this.state.dbData); // Shows correct data
},100);
I would like to try to find a way for this to work without the artificial delay. I will need to do about 3 of these database queries in componentWillMount as my component is loading and will need to know that I have the data back before I render the component.
Thanks!
Use the componentDidMount lifecycle hook to obtain async data when a component is initialized. This should be done in the component that is using the asynchronously obtained data, or the closest common ancestor for data that is used by multiple components. The reason for this is to reduce the amount of re-rendered components once the async retrieval has completed.
Keep in mind you will also need to account for a loading state, before your async data is available.
Below is a basic example of the principles.
class ComponentThatRequiresAsyncData extends PureComponent {
constructor( props ) {
super( props );
// initialize state
this.state = {
data: null
}
}
// handle async operation in this lifecycle method to ensure
// component has mounted properly
componentDidMount() {
axios.get( "some_url" )
.then( ( response ) => {
// once you have your data use setState to udpate state
this.setState( ( prevState, props ) => {
return { data: response.data };
})
})
.catch( ( error ) => {
// handle errors
});
}
render() {
const { data } = this.state;
return (
{
data ?
<WhatEverIWantToDoWithData data={ data } /> :
<p>Loading...</p>
}
);
}
}
Use the same idea for data that needs to be loaded because of an event, such as a button click. Instead of using componentDidMount you would make your own method, make your async call and update state via setState in the then method of your http call. Just make sure you bind the this context of your method in your constructor if you're using it as an event handler.
Hope this helps. If you have any questions please ask!
I have a method that toggles a boolean value in state by copying the value then updating state:
toggleSelected = () => {
let selected = this.state.lists.selected;
selected = !selected;
this.setState({
// update state
});
};
I have another method, fired by an onClick handler, that calls toggleSelected twice:
switchList = (listName) => {
const currList = this.getCurrentList();
if(listName === currList.name) return;
this.toggleSelected(listName);
this.toggleSelected(currList);
};
However it appears that the state doesn't finish getting set from the first call by the time the second call runs; if I set a timeout on the second call, it works fine.
What's the correct way to do this?
An alternative to what #SLaks suggested, useful sometimes, is using the setState(new_state, callback) method. The callback will be run once the state is updated and "visible".
In general, setState will not change the state immediately so that it is visible in this.state. From the docs
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall.
As noted in the React State/Lifecycle docs, the correct way to update state based on previous state is by passing a function into the setState() call, eg:
toggleSelected() {
this.setState((prevState, props) => {
return {
lists: {
selected : ! prevState.lists.selected
}
};
});
}
This is why you should always pass a lambda to setState(), so that it will give you the actual current state:
this.setState(state => ({ ..., selected: !state.lists.selected }))