Why can't I set server's answer to state? - javascript

I'm trying to get JSON from api.openweathermap.org and set it to state, but as result I get console.log
What should I do set JSON's info to state.weather?
import React, { Component } from 'react';
class GetWeather extends Component {
constructor(props) {
super(props);
this.state = {
weather: {},
temp: ''
}
};
weather = async (e) => {
e.preventDefault();
try {
let response = await fetch('http://api.openweathermap.org/data/2.5/weather?q=London,uk&APPID=b40640de9322c8facb1fcb9830e8b1f4');
let data = await response.json();
// if I will use response.text() here, than next console.log will show me the object literal that I got from server
console.log('data: ' + data);
await this.setState({weather: data});
console.log('state ' + this)
} catch (e) {
console.log(e);
}
}
render() {
return (
<button onClick={this.weather} />
)
}
}
export default GetWeather;

React state updates are asynchronous and occur at the end of the function call (i.e. all set state calls are "collated" and processed together, part of the reconciliation), so console logging the state update immediately after doesn't work.
Try to use the setState callback
this.setState({weather: data}, () => console.log('state', this.state));
State will update and call the callback afterwards, synchronously, so you'll see the new state value. You also do not need to await it.

You cannot await setState. To execute code after your state has changed, setState actually has 2nd argument which is a callback function that is executed after the state has changed. Your code should look something like this:
console.log(data);
this.setState({weather: data}, () => {console.log(this.state)});
Here you can see another problem. Since you are concatenating a string ('data:') with an object, your object is converted to the string and you get [object Object]. To avoid this, print either only the object or print an object separately from the string like this: console.log('data:', data). Note that I used a comma here, not a plus.

Related

Why useState not apply new value

I use hook useState for set post value.
const [firstPost, setFirstPost] = useState();
useEffect(() => {
(async () => { await onFetchPosts(); })();
}, []);
const onFetchPosts = async () => {
try {
const { body } = await publicService.fetchPostById(119);
// get post
const post = body.posts;
if (post && post.postsId) {
console.log(`save...`, body.posts);
setFirstPost(body.posts);
}
console.log(`firstPost...`, firstPost);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
}
I dont understand, firstPost is not updated.
This is because setState calls are asynchronous. Read it here and here. As the per the doc I linked
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state
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
Therefore, the state is usually not updated yet when you console.log it on the next line, but you can access/see the updated state on the next render. If you want to log values, you can put them as inside a <pre> tag in your HTML, or do console.log at the beginning, like below:
const [firstPost, setFirstPost] = useState();
// Console.log right on at the start of the render cycle
console.log("First post", firstPost);
useEffect(() => {
(async () => {
await onFetchPosts();
})();
}, []);
const onFetchPosts = async () => {
try {
const { body } = await publicService.fetchPostById(119);
// get post
const post = body.posts;
if (post && post.postsId) {
console.log(`save...`, body.posts);
setFirstPost(body.posts);
}
// Do not console.log the state here
// console.log(`firstPost...`, firstPost);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
};
// Can also debug like this
return <pre>{JSON.stringify(firstPost)}</pre>;
React may batch multiple setState() calls into a single update for performance.
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.
https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
React does not change the state variables immediately when state is changed i.e why you are getting undefined in console.
However the new value of firstPost is assured to be there in the next render.

How can I set state in a function that is in a utility file?

I had a function in a react class that I was using to pull data.
It was working great, but I then found out I needed to use the same function in a few other places.
So I decided to put the function in it's own file so I could re-use it.
So here it is:
import axios from 'axios';
export const getGalaxyName = async (id) => {
try {
const { data: response } = await axios.get(`/api/scienceClass/galaxy/${id}`)
this.setState({ galaxyName: response.name });
}
catch (error) {
console.log(error);
}
}
And then in a component, I use it like this:
import { getGalaxyName } from './ScienceClassUtils';
render() {
getGalaxyName(slide.z_GalaxyId);
But now I am getting this error:
ScienceClassUtils.js:35 TypeError: Cannot read property 'setState' of
undefined
I am guessing it's because I'm trying to still set the state in the function like I did when it was originally inside the react class.
So how can I still use it now that it's separated out in another file, but still have this.setState({ galaxyName: response.name }); ?
Thanks!
Easiest way from here to there might be to have it return the promise instead of calling setState directly:
const { data } = await axios.get(`/api/scienceClass/galaxy/${id}`)
return data.name;
Then your component or anyone else could do whatever they need to do with it:
getGalaxyName(id).then(galaxyName => this.setState({ galaxyName });
There are at least two possible approaches:
Create a helper method for setting the galaxyName and then pass it to axios utility, and it will call it and pass the response.name to it.
Return the response from axios utility and use Promise methods .then, .catch, .finally, to handle the success, fail, finished cases as needed. Note that, the data you return from the axios utility will be passed to these methods as parameter.
The bottom line is you can't set the state outside a React component. Make your utility function return the data you need and set the state from within your react component.
Refactor your util function to look like this
import axios from 'axios';
export const getGalaxyName = async (id) => {
try {
const { data: response } = await axios.get(`/api/scienceClass/galaxy/${id}`)
return { galaxyName: response.name }
} catch (error) {
console.log(error);
}
}
From within your component call the method in a componentDidMount. lifecycle hook like this
class SampleComponent extends React.component {
// Add this lifecycle hook
componentDidMount() {
getGalaxyName(slide.z_GalaxyId).then((data) => {
this.setState(data)
});
}
render() {
// use state data as needed here
}
}

React redux setting initial state from axios call

I have a React app which uses Redux and axios. I do not want anything to render before I have some information from the server, which I am retrieving via axios.
I thought that the best way to do this would be by initializing redux state based on that axios call.
However, my function does not seem to be returning anything in time for state initialization...
function getUserData() {
if (Auth.loggedIn()) { //leaving this here bc it could be important; Auth is another class and loggedIn returns a boolean
axios.get('/route').then(res => {
console.log(res.data); //This prints the right thing (an object)
return res.data;
});
} else {
return ' '; //This works fine; state gets initialized to ' '
}
}
let userData = getUserData();
console.log(userData); //When getUserData() returns ' ', this prints ' '. However, when getUserData() returns my object, this prints undefined.
const initialState = {
userData: userData
};
I realize that this could be a problem with getUserData() being asynchronous, and console.log(userData) running before getUserData() has finished. However, I tried:
getUserData().then(function(userData) {
console.log(userData);
});
And received 'TypeError: Cannot read property 'then' of undefined'. My function is obviously not returning a promise, so doesn't that mean it's not asynchronous?
Any ideas?
Alternatively, is there a better way of doing this? I could always set initial state and then immediately change it, and make rendering wait for the change to be complete with a conditional render, but that definitely seems worse.
You have to return promise from your getUserData function and access it using .then().
function getUserData() {
return new Promise((resolve, reject) => {
if (Auth.loggedIn()) {
axios.get('/route')
.then(res => resolve(res.data))
.catch(err => reject(err));
} else {
resolve(' ');
}
});
};
getUserData().then(function(userData) {
const initialState = {
userData: userData, // ' ' or axios result
};
console.log(initialState.userData)
});

this.setState not working on componentWillMount()?

I want the state to be dependent on server data. I thought of using componentWillMount:
componentWillMount() {
this.setState( async ({getPublicTodosLength}, props) => {
const result = await this.getPublicTodosLengthForPagination();
console.log("result = ", result) // returns the length but not assigned on this.state.getPublicTodosLength
return { getPublicTodosLength: result+getPublicTodosLength }
});
}
getPublicTodosLengthForPagination = async () => { // get publicTodos length since we cannot get it declared on createPaginationContainer
const getPublicTodosLengthQueryText = `
query TodoListHomeQuery {# filename+Query
viewer {
publicTodos {
edges {
node {
id
}
}
}
}
}`
const getPublicTodosLengthQuery = { text: getPublicTodosLengthQueryText }
const result = await this.props.relay.environment._network.fetch(getPublicTodosLengthQuery, {})
return await result.data.viewer.publicTodos.edges.length;
}
There is value but it's not assigned on my getPublicTodosLength state? I think I don't have to bind here since result returns the data I wanted to assign on getPublicTodosLength state
Why not rather do something like this?
...
async componentWillMount() {
const getPublicTodosLength = this.state.getPublicTodosLength;
const result = await this.getPublicTodosLengthForPagination();
this.setState({
getPublicTodosLength: result+getPublicTodosLength,
});
}
...
It's simpler and easier to read. I think the problem with the original code is with using async function inside setState(). In transpiled code there is another wrapper function created and then it probably loose context.
If you want your state to be dependent on server data you should use componentDidMount().
componentWillMount() is invoked immediately before mounting occurs. It is called before render(), therefore setting state synchronously in this method will not trigger a re-rendering. Avoid introducing any side-effects or subscriptions in this method.
This is the only lifecycle hook called on server rendering. Generally, we recommend using the constructor() instead.
componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here. If you need to load data from a remote endpoint, this is a good place to instantiate the network request. Setting state in this method will trigger a re-rendering.
From React Doc
May be you could use:
Code snippet:
(async ({getPublicTodosLength}, props) => {
const result = await this.getPublicTodosLengthForPagination();
console.log("result = ", result);
return { getPublicTodosLength: result + getPublicTodosLength }
})).then((v)=> this.setState(v));
Please let me know if that works.
i decided to make componentWillMount async and it worked well.
this is the code:
componentWillMount = async () => {
let result = await this.getPublicTodosLengthForPagination();
this.setState((prevState, props) => {
return {
getPublicTodosLength: result
}
});
}

How to set state of response from axios in react

How do I set the state of a get response in axios?
axios.get(response){
this.setState({events: response.data})
}
You have a syntax error here. You should try this instead
var self = this;
axios.get('/url')
.then(function (response) {
console.log(response);
self.setState({events: response.data})
})
.catch(function (error) {
console.log(error);
});
//the rest of the code
var a = 'i might be executed before the server responds'
There are a few things to note here:
axios.get is an asynchronous function which means that the rest of the code will be executed .And when the response of the server arrives, the function passed to then will be executed. The return value of axios.get('url') is called a promise object. You can read more about it here
this keyword has a different value depending of where it is called. this in this.setState should refer to the constructor object, and when you call this inside a function, it refers to the window object. That is why i assigned this to the variable self. You can read more about this here
Pro tip:
If you use ES6, you would want to use arrow functions (which don't have their own this) and use this.setState without assigning this to a variable. more about it here
axios.get('/url')
.then((response) => {
console.log(response);
this.setState({events: response.data})
})
.catch((error)=>{
console.log(error);
});
Here is a complete example https://codesandbox.io/s/rm4pyq9m0o containing best practices commonly used to fetch data including error handling, try again and loading. This provides a better User experience. You are encouraged to modify the code and play around to get more insights about it.
This isn't working because "this" is different inside of axios. "this" inside axios refers to the axios object, not your react component. You can resolve this with .bind
Also axios isnt being used properly.
it should look something like
axios.get("/yourURL").then(function(response) {
this.setState({ events: response.data });
}.bind(this));
Alternatively if using es6 you could sub out the function for an arrow function and get the same effect without bind
axios.get("/yourURL").then(response => {
this.setState({ events: response.data });
});
Simply try this node js
axios.get(`https://jsonplaceholder.typicode.com/users`)
.then(res => {
const persons = res.data;
this.setState({ persons });
})
if you are using react js then you first import in component than use axios
like this:
import React from 'react';
import axios from 'axios';
export default class PersonList extends React.Component {
state = {
persons: []
}
componentDidMount() {
axios.get(`https://jsonplaceholder.typicode.com/users`)
.then(res => {
const persons = res.data;
this.setState({ persons });
})
}
render() {
return (
<ul>
{ this.state.persons.map(person => <li>{person.name}</li>)}
</ul>
)
}
}
I have dealt with promises similar to that in the past when I was learning react. What I did was put the api call on the componentDidMount method and set the state to an initial value. I used a loader while the data was being fetched.
componentDidMount() {
const self = this;
axios.get(response){
self.setState({ events: response.data });
}
As of now, I would use something similar to what checkenrode said.
Do something like this:
var self= this; // self will now be referred to your component
axios.get("http://localhost:3001/get_user?id=" + id)
.then(function (response) {
if(response.data.rows != null)
user_detail = response.data.rows;
console.log(response);
self.setState({email: user_detail.name, name: user_detail.name})
})

Categories

Resources