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

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.

Related

Why my function in componentDidMount is being called rapidly?

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

What's the best way to store a global variable for a reusable React component?

I'm building a React component (think a button) that makes a network request when clicked. This component may be rendered multiple times on the same page, but the network request only needs to happen once. Therefore, I want to store a variable, networkRequestWasMade, to check against before making the request.
Normally, I'd find a common parent and wrap it in React's context mechanism. But, this component will be part of a reusable component library that will be used across many applications (think hundreds), so I would prefer that it doesn't rely on a specific parent component or context to store this value. I am brainstorming about what would be the best way to achieve this.
A very simple solution would be to attach a boolean to the window object:
function Button() {
const handleClick = () => {
if (!window.networkRequestWasMade) {
// ...make request...
window.networkRequestWasMade = true
}
}
return <button onClick={handleClick}>Click me!</button>
}
However, I'm wondering if there's a better pattern for this.
Try to not use global variables, this is a bad practice. You just need to store the promise into a variable and initialize it only the very first time.
Here is an example using axios to make the http call
// util.js
let p;
function loadData() {
if (!p) {
p = axios.get('uri')
.then(response => result = response.data);
}
return p;
}
Then your component can call the function loadData multiple times, the http call will be executed only once
// Button.jsx
import { loadData } from './util'
function Button() {
const handleClick = () => {
loadData()
.then(data => {
// do what you want with the data
})
}
return <button onClick={handleClick}>Click me!</button>
}
Maybe the http request is not a GET and you don't care about the result but you get the idea.

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.

Proper way to wait for async data to store in state in React or React Native

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!

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