Why my function in componentDidMount is being called rapidly? - javascript

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

Related

React - how to show propup based on API-result?

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.

React JS : Get data from api and send to child component

I'm a little bit lost. I want to get a response from my API and send the data to a child component. It seems that I don't understand how to do that because it says to me : "TypeError: Cannot read property 'setState' of undefined"
Can you explain to me how can I do it ?
Here is a snippet of my code :
function GetData(url, params, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url + '?' + params, true);
xhr.onreadystatechange = function(e) {
if (this.readyState === xhr.DONE) {
if (this.status === 200) {
callback(JSON.parse(xhr.responseText));
} else {
console.log('error');
}
}
}
xhr.send(null);
}
class Utilisateurs extends Component {
constructor(props){
super(props);
this.state = {users:''}
GetData('http://localhost:3333/shop/users', 'login=true',
function(res) {
this.setState({users: res});
}
)
}
render() {
return (
<div id="center">
<UserListSorted data={this.state.users}></UserListSorted>
</div>
);
}
}
So I make a GET request and I want to change the state of 'users' with the response so I can print a list of users. But I don't know how to fix this.
Thank you for your help!
There is a couple things going on here.
First, you shouldn't make network requests in the constructor or componentWillMount(). Someone will probably call it an anti-pattern.
You shouldn't ever do any long running tasks from the constructor because it will make the component take longer to load. It is a good general rule. Just use it to set variables and properties and trigger other class methods, quick setup. I can't remember exactly, but it's probably an issue with synchronous vs. asynchronous behaviour. The constructor is a method. It is a function, so if you hang the thread waiting for network request, the component has to wait before it can continue.
The same is true for componentWillMount(). That lifecycle method kicks off before the component is mounted in the DOM, which means if you make a network request and it completes before the component is mounted, there is nothing to setState on. There is no component in the DOM yet to setState. This is what someone told me before and it stuck with me. I am just telling you now to hopefully give you that same moment of realization.
You could do it from componentDidMount() because the component is ready, but it will trigger another re-render if you call this.setState() from inside that lifecycle method. Some call "setState inside componentDidMount" an anti-pattern, but its not really that bad. I would say use it, but be aware that it does trigger a re-render. The render method will run at least twice. If you had a big, complex component or lots of them, it could cause performance issue. That's why people say "don't do it".
Moving forward, I would recommend creating a class method called getData() or something and call that from componentDidMount(). Something like this:
class Something extends Component {
constructor(props) {
super(props)
this.state = {
data: '',
}
}
componentDidMount() {
this.getData()
}
getData() {
// get data from server
const theData = 'we got some data'
this.setState({
data: theData,
})
}
render() {
return <div>{this.state.data}</div>
}
}
I actually can't exactly help you with the XHR request because its been al ong time since I used it. I can't verify if your syntax is correct. I would recommend using a library called request https://www.npmjs.com/package/request. I imagine the XHR request will work fine, but check out request as a learning exercise.
The this context in this.setState isn't what you think it is. You can reassign this to a new variable outside of the closure, but it's a lot easier if you use an arrow function (which has no this and borrows the context from the outer scope) instead:
GetData('http://localhost:3333/shop/users', 'login=true', (res) => {
this.setState({ users: res });
});
Aside: while you can perform fetch operations in the constructor the React team recommend that you use componentDidMount instead. There's more information in this article.
Oh, you you might think about using fetch instead of writing all that XMLHTTPRequest code.
Problem is with the context of this, inside GetData function it's undefined, so try using arrow function for your callback () => {} which automatically binds this context from Utilisateurs class.
GetData(
'http://localhost:3333/shop/users',
'login=true',
res => {
this.setState({users: res});
}
)
You should do your api calls in componentDidMount (in addition to use an arrow function as the callback of GetData()) :
class Utilisateurs extends Component {
constructor(props){
super(props);
this.state = {users:''}
}
componentDidMount() {
GetData('http://localhost:3333/shop/users', 'login=true',
(res) => {
this.setState({users: res});
}
)
}
render() {
return (
<div id="center">
<UserListSorted data={this.state.users}></UserListSorted>
</div>
);
}
}
Edit : Actually componentDidMount is recommended in React Docs, they don't explains why unfortunately, but some articles about best practices in React tells that fetching before the first render can lead in issues some cases.

Is it a bad idea to call a function mapped to a property and use that function's output immediately when using react-redux?

I have a question about the following example code, which seems to have something to do with redux data flow inside.
// action.js
export function doSomething() {
return {
type : 'FOO',
data : 'BAR'
};
}
// reducer.js
...
case types.FOO :
return update(state, { mystatus : { $set : action.data } });
...
// container.js
class MyTestClass {
...
handleButtonClick() {
this.props.doSomething(); // doSomething is just simple, synchronous action creator. In this function, state.foo
console.log(this.props.status); // expected output should be 'BAR' but, actual output is 'undefined'
}
...
}
const mapStateToProps = (state => {
return {
status : state.mystatus
};
};
const mapDispatchToProps = (dispatch) => {
return {
doSomething : () => {
return dispatch(doSomthing());
}
};
}
export default connect(mapStateToProps, mapDispatchToProps)(MyTestClass);
Now, this is my question.
If you see the code 'this.props.doSomething(); console.log(this.props.status);' in handleButtonClick method, the output of this code is supposed to be 'BAR'. However, I got 'undefined'.
this.props.doSomething();
console.log(this.props.status); // output is 'undefined'
And, If I change that part like following, I could get what I expected.
this.props.doSomething();
setTimeout(() => { console.log(this.props.status); }, 0); // now output is 'BAR'!!
I thought that "calling this.props.doSomething() => creating action => dispatching => mapping the changed state to the property by redux" was a series of synchronous control flow, but it does not seem to be.
There seems to be an asynchronous control flow in the process of being dispatched.
Is it a bad idea to call a function mapped to a property and use that function's output immediately when using react-redux? Could you explain how react-redux handle this inside?
Thnak you.
According to this, React Redux uses setState under the hood.
If you use bindings like React Redux, this is the point at which component.setState(newState) is called.
According to this, setState() is asynchronous and batched for performance gains.
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
Also,
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
So if you want to react to state changing in terms of view, you should use renderhook for it. If you want to react to dispatching some action in terms of actions (logging, initiating another action), you should create middleware (generic cases) or use redux-saga/redux-thunk (receiving actual particular event and do something) depending on your task type.

How to handle asynchronous calls to setState in React?

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

Asynchronous call in componentWillMount finishes after render method

I am trying to perform an asynchronous call to an API in the componentWillMount method. Indeed I would like the render method to executed after the componentWillMount method as I need to pass props to the component in my render method.
Here is my code :
class TennisSearchResultsContainer extends React.Component {
componentWillMount () {
// TODO: Build markers for the map
// TODO: Check courtsResults object and database for tennis court
this.courtsMarkers = this.props.courtsResults.map((court) => {
return new google.maps.Marker({
position: new google.maps.LatLng(JSON.parse(court.LOC).coordinates[1], JSON.parse(court.LOC).coordinates[0]),
title: court.NAME,
animation: google.maps.Animation.DROP
});
});
}
render () {
return <TennisSearchResults criterias={this.props.criterias} courtsMarkers={this.courtsMarkers} />;
}
}
I don't understand then why my render method seems to do not wait for the asynchronous call to finish and pass undefined props to my child component...
Am I right? And what should I do to fix that? What is the way to handle this?
You might need to understand javascript async behavior better. Async means "don't wait". That the task will happen in the background and other code will continue to execute. A good way to manage this is to set state on your component. For example, when you enter componentDidMount set a loading state to true. Then when your async function completes, set that state to false. In your render function you can then either display a "loading..." message or the data.
Here is some code that shows a simplified example of fetching data async and how you could handle that in React. Open the developer tools in your browser and look at the console output to understand the React lifecycle better.
EDIT: Code has been updated to use the new React Lifecycle recommendations as of April 2018. In summary, I replaced componentWillMount with the safer componentDidMount.
It might seem inefficient to update the state after the component has already mounted, as 'componentDIDmount' correctly implies. However, per the official React documentation on componentDidMount:
"If you need to load data from a remote endpoint, this is a good place to instantiate the network request."
"Calling setState() in this method 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."
Here's the complete example code:
class MyComponent extends React.Component {
constructor(props) {
super();
console.log('This happens 1st.');
this.state = {
loading: 'initial',
data: ''
};
}
loadData() {
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('This happens 6th (after 3 seconds).');
resolve('This is my data.');
}, 3000);
});
console.log('This happens 4th.');
return promise;
}
componentDidMount() {
console.log('This happens 3rd.');
this.setState({ loading: 'true' });
this.loadData()
.then((data) => {
console.log('This happens 7th.');
this.setState({
data: data,
loading: 'false'
});
});
}
render() {
if (this.state.loading === 'initial') {
console.log('This happens 2nd - after the class is constructed. You will not see this element because React is still computing changes to the DOM.');
return <h2>Intializing...</h2>;
}
if (this.state.loading === 'true') {
console.log('This happens 5th - when waiting for data.');
return <h2>Loading...</h2>;
}
console.log('This happens 8th - after I get data.');
return (
<div>
<p>Got some data!</p>
<p>{this.state.data}</p>
</div>
);
}
}
ReactDOM.render(
<MyComponent />,
document.getElementsByClassName('root')[0]
);
And here is the working example on CodePen.
Finally, I think this image of the modern React lifecycle created by React maintainer Dan Abramov is helpful in visualizing what happens and when.
NOTE that as of of React 16.4, this lifecycle diagram has a small inaccuracy: getDerivedStateFromProps is now also called after setState as well as forceUpdate. See this article from the official React blog about the Bugfix for getDerivedStateFromProps
This interactive version of the React lifecycle diagram created by Wojciech Maj allows you to select React version >16.04 with the latest behavior (still accurate as of React 16.8.6, March 27, 2019). Make sure you check the "Show less common lifecycles" option.
The above answer is probably overkill for what you are trying to do. All you need to do is make compoentDidMount an async function. Then you can use the await keyword on a function call that returns a promise.
class MyComponent extends React.Component {
async componentWillMount () {
await myAsyncCall();
}
render () {
}
}

Categories