i am trying to get data from an external API and use it to change the state on my app. The data actually shows up in the console but when i run set state does not change the state of my app.
class App extends Component {
state={
jobs:[]
}
onTermSubmit=(term)=>{
const proxy=`https://cors-anywhere.herokuapp.com/`;
const api = `${proxy}https://--------/positions.json?description=${term}&page=1`;
fetch(api).then(res=>res.json()).then(data=>this.setState({jobs:data}))
console.log(this.state)
}
I am trying to get the state to change to the data returned from the API
Both fetch() and setState() are asynchronous so attempting put a console.log() statement after the expression will not wait for the fetch or setState() to complete/resolve. Instead you can use the callback argument of setState() to console.log() only after fetch has resolved and setState() has updated state. From the documentation:
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. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
fetch(api)
.then(res => res.json())
.then(data =>
this.setState({ jobs: data }, () => {
console.log(this.state);
})
)
.catch(err => console.log(err));
Hopefully that helps!
As mentioned by #Alexander Staroselsky fetch() and setState() are asynchronous operations and the application won't wait until they are done to continue to the next operation thus resulting in this.state.jobs to still be an empty array.
My solution would be to make them synchronous by adding the async/await keyword before calling he function like this.
onTermSubmit= async (term)=> {
const proxy=`https://cors-anywhere.herokuapp.com/`;
const api = `${proxy}https://--------/positions.json?description=${term}&page=1`;
let rawRes = await fetch(api)
let jsonRes = await rawRes.json()
await this.setState({jobs:rawRes}))
console.log(this.state)
}
Just another approach
Hope this is helpful
Related
I'm working on a project where we would like run multiple fetches in parallel to a very slow API. Ideally, we would like to populate our interface for the user as this data is received and do so in a summative manner. These requests may or may not resolve in the order that the API calls were made.
Most use cases of Promise.all with a setState involve setting state after all promises have resolved. However, what I'm looking to do demands setting state as a side effect within the child promises themselves, I believe.
So this is (simplified) what I am doing to achieve this:
const request = async (setState, endpoint) => {
const response = await fetch(endpoint);
const data = response.json();
setState(state => ({ ...state, ...data }))
}
// Called within React component as a side effect
const fetchAllData = (setState) => {
Promise.all(
[
request(setState, url_1),
request(setState, url_2),
request(setState, url_3)
]
)
}
Now, I'm running some testing and this does appear to work. I believe I should not be running into race conditions with the state because setState is being passed a function. However, I do wonder if I'm doing something dangerous with respect to React, updating state, and rendering.
Is there anything wrong with this picture?
There's nothing wrong with immediately updating state from each individual promise; this will work just fine. You might have a race condition if every request attempts to update the same bit of data, but as long as they write different parts of your state you should be fine (the state updater callback pattern is necessary though).
The only thing wrong with your code is the missing error handling, and that the Promise.all is currently a bit superfluous.
I am confused right now. Learning js frontend and how to make API requests.
Till now, whenever I made an API call, I always used something, that let me handle an asynchronous request (axios or thunk).
But now I got a case, where I am making a request to my Firebase/Firestore and in the yt video, that I watched, only useEffect was used. Without any async/await, like:
useEffect(() => {
// snapshot - to check, if there is a change (like a new entry) in the database
db.collection('products').onSnapshot((snapshot) => {
setProducts(snapshot.docs.map((doc) => doc.data()));
});
}, []);
Why is this?
In the example code snippet
useEffect(() => {
// snapshot - to check, if there is a change (like a new entry) in the database
db.collection("products").onSnapshot(snapshot => {
setProducts(snapshot.docs.map(doc => doc.data()))
})
}, []);
db.collection("products") manages its own asynchronous event handling. When a "snapshot" event is received it invokes the onSnapshot callback and updates state. There is nothing to await on, especially since there is no value returned.
You should also note that useEffect hook callbacks are 100% synchronous code, they can't be marked async and await any code calls. They can, however, call async functions that do their own awaiting and such.
About the questions in your title
Use a useEffect hook any time you want to issue any side-effect from the component lifecycle, like console logging state, make data fetches, dispatching actions. This is mostly due to the fact that the entire function body of a functional react component is to be considered a pure function. useEffect with dependencies is more akin to a class-based component's componentDidMount, componentDidUpdate, and componentWillUnmount lifecycle methods. Alone they are just called as part of the react component lifecycle.
Use axios, or fetch, or any other data fetching code whenever you need to fetch that data.
useEffect() - It is a hook that allows us to perform side effects in functional components like network request etc.
Axios - It is just a library where we can make http request like fetch API in javascript and also axios is promise based.
I know that calling setState method means I don’t have to manually invoke the ReactDOM.render method. Below is some example code:
...
render() {
return (
<button onClick={this.handleClick}>
Click
</button>
)
}
handleClick = () => {
this.setState({ counter: this.state.counter + 1 }, () => this.setState({ hasButtonBeenClicked: this.state.counter > 0 }));
this.props.callback();
}
since there is another statement this.props.callback(); below the this.setState() method, so does ReactDOM.render method get called before or after this.props.callback();?
since there is another statement this.props.callback(); below the this.setState() method, so does ReactDOM.render method get called before or after this.props.callback();?
After, in your case. The state update and call to render is asynchronous if setState is called within a React event handler (and may be asynchronous even if not, more here). The sequence (within a React event handler) is:
You call setState with the state update.
You call this.props.callback();
React processes the state update (possibly combining it with other pending updates).
(Some handwaving here about shouldComponentUpdate.)
React calls render to have it render the current state.
If you want this.props.callback(); called after the new state has been rendered, put it in a function as the second argument of setState:
handleClick = () => {
this.setState(
{ counter: this.state.counter + 1 },
() => {
this.props.callback();
}
);
}
or
handleClick = () => {
this.setState(
{ counter: this.state.counter + 1 },
this.props.callback
);
}
...if this.props.callback doesn't care what this you call it with.
More here:
State Updates May Be Asynchronous (that title drives me nuts, because it's not that they may be asynchronous, it's that they are asynchronous)
setState API docs
If you look at my example of your code, this.props.callback() gets called immediately after the state has been updated.
handleClick = () => {
this.setState({ counter: this.state.counter + 1, hasButtonBeenClicked: true }, () => this.props.callback() );
}
You have chained setStates, which seem unnecessary for readability. These should be grouped into a single setState call automatically, however one is nested into the callback and this would trigger multiple re-renders.. depending on ShouldComponentUpdate.
To avoid guessing or leaving it susceptible to future React updates:
Using the setState callback for this.props.callback, is the best way to ensure it is executed after setState completes.
Updated, based on T.J Crowders feedback and research with event handlers:
The way your code was structured it is most probable that this.props.callback will be called prior to the setState actually completing state updates, it will trigger the re-render once state updates.
-Because, it is in an asynchronous call and within a React event handler. SetState should update state after.(I am still no expert on promises, and have to wonder if there is a chance state could update within the millisecond, depending on your browsers internal clock and the batch processing)
For clarity, to others. This example is asynchronous and that means the code continues to be executed, while waiting for resolution. While setState returns a promise. In this case, it should absolutely continue to process this.props.callback(), until the promise is resolved.
I am having issue with state as i'm not 100% i'm using componentDidMount and componentWillMount correctly.
I have set the constructor and super props, and I am getting a user object with the getCurrentUser() method and setting the user state with the new object.
componentWillMount() {
const current_user = getCurrentUser().then(user => {
this.setState({
user: user
});
console.log(user);
});
}
componentDidMount() {
console.log(this.state.user);
}
It logs the user correctly in componentWillMount, but logs an empty object in componentDidMount.
Any guidance would be massively appreciated!
Simply don't use componentWillMount,
Do it in componentDidMount.
In practice, componentDidMount is the best place to put calls to fetch data, for two reasons:
Using DidMount makes it clear that data won’t be loaded until after the initial render. This reminds you to set up initial state properly, so you don’t end up with undefined state that causes errors.
If you ever need to render your app on the server (SSR/isomorphic/other buzzwords), componentWillMount will actually be called twice – once on the server, and again on the client – which is probably not what you want. Putting the data loading code in componentDidMount will ensure that data is only fetched from the client.
getCurrentUser is an asynchronous method which calls another asynchronous method (setState).
I am pretty sure that you will first see the log entry from componentDidMount and only afterwards the log entry from componentWillMount.
What's happening is:
React calls componentWillMount
You start an async call (getCurrentUser)
componentWillMount returns immediatelly, without waiting for the promise to complete
React calls componentDidMount
Your promise resolves
The logs are due to the asynchronous nature of your method getCurrentUser. When you call getCurrentUser in componentWillMount, it might result in an output after the componentDidMount has finished executing and hence you see the initial state in componentDidMount. However the console.log in componentWillMount is in the getCurrentUser .then promise callback which will log the current value received from getCurrentUser()
I am making a fetch request that returns an array of strings.
fetch(linkFetch)
.then(resp => resp.json())
.then(arr => {
that.setState({
images: arr
});
})
.then(this.test());
on the last line you can see I try to call the method test() which is just trying to access the state that was set in the fetch() request.
test(){
console.log(this.state.images[1]);
}
console logs 'undefined'
However, if I assign test() to a button or something so that I can manually call it works fine which leads me to believe that when I call .then(this.test()); in the fetch request it is actually being called before the state is set.
How can I make sure it gets called after the state has been set in the fetch request?
The argument you pass to then needs to be a function.
You are calling this.test immediately (i.e. before the asynchronous function has resolved) and passing its return value (undefined as there is no return statement).
You already have an example of how to do this correctly on the previous line of your code.
fetch(linkFetch)
.then(resp => resp.json())
.then(arr => {
that.setState({
images: arr
});
})
.then(() => {
this.test();
});
React provides a way to call a function after the setState has been executed. You can use the callback function as described in the react documentation
https://reactjs.org/docs/react-component.html#setstate
You can pass your desired function as a second argument to the setState call.
that.setState({
key:Val}, that.test);