How to fix logging issues when setting state in componentWillMount - javascript

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

Related

How to wait for async mounted of Vue component to finish before continuing with testing

I have a Vue2component with a async lifecycle hook method:
// create report when component is loading
async mounted(): Promise<void> {
await this.createReport();
}
Now I would like to test my component via jest and vue/test-utils but the test should await the completion of the component's mounted() method.
const wrapper = await mount(MyComponent, { // <-- no Promise, await has no effect
localVue
});
await flushPromises(); // <-- is not waiting for the async mounted()
expect(wrapper.vm.currentReport.sections).toHaveLength(5); // <-- failing since createReport not finished
Unfortunately mount() or shallowMount() do not return a Promise and will not wait for the completion of the lifecycle hook.
So far the only option was await flushPromises() which has no effect on the mounted method.
Others wrote await mount() but since no Promise is returned, this has also no effect.
I think most tests pass, because they are just fast enough so the mounted is finished already.
Which is not the case for me, since we are loading some proper data and that takes a few seconds.
A user will see a loading screen and will wait patiently to interact with the component, jest unfortunately will not wait.
How can I start my Vue tests with jest after the mounted-logic has finished?
If you mark mounted as async you are not preventing the component from mounting until the promise resolved/rejected 1. It will mount and, at some later point, mounted will resolve/reject.
If you want to wait until something happens (e.g: this.createReport() resolves), inside your mounted, after the await, set a boolean to true (e.g: this.hasReport).
Now you can watch that reactive data value and trigger some other functionality or you can use it inside your <template> to prevent some DOM from rendering before it has the necessary data.
The typical way to test async behaviour, is to actually mock the async response. You haven't shown us what createReport() does but it's likely external to the component logic. For unit tests, it doesn't really matter what it does.
You should mock all the cases you want to test (resolve success, reject failure, ...).
Why mock? Because, for example, you don't want your tests to fail if the internet is not working when you run the tests. So you shouldn't be making an actual http request to some distant API. You should replace that call with a function (mock), returning a promise resolving with the expected data. Or rejecting with the expected error.
All testing frameworks (jest, vitest, etc...) make mocking a breeze.
See testing async behavior.
1 - Technically, this statement is only 100% true in Vue 2. Vue 3 has a built-in experimental <Suspense> component, which has two slots: the content and the fallback. If any component, no matter how deep, inside the content slot has asynchronous created and/or mounted hooks, the <Suspense> component will render the fallback slot contents, until all the content promises resolve or reject. When all of them have, it will display the content slot.
This is not relevant for your case, since you're using Vue 2, but I had to specify it to avoid confusion for Vue 3 users.

Confusion with how setState works

I am confused with how setState works.
Here is a portion of my code:
handleBarsChange(value) {
this.setState({numberOfbars: value});
this.resetArray();
}
apparently, when the resetArray() function executes and tries to access the this.state.numberOfbars state variable, setState hasn't updated the value yet.
I want some explanation on how and where should the setState be executed and when can i expect to see the change.
Thank you!
setState is asynchronous. It has a second callback argument that can be used to run code after a state transition. This isn't always the best approach, but with the very narrow example provided, it's hard to say.
this.setState({numberOfBars: value}, () => { this.resetArray() })
From the react docs:
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.
https://reactjs.org/docs/react-component.html#setstate
The fact is that setState() function is async so even if you run code after you call it, some state may not be updated yet.
The solution is implement a callback when the state was updated succesfully (setState(<state>, <callback>)), for example:
handleBarsChange(value){
this.setState({numberOfbars: value}, () => {
this.resetArray();
});
}

When should I use axios for async calls and when only useEffect?

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.

Promise chaining to a possibly resolved promise

I have an application which, depending on the state of the form, on submit, may have to make 0-3 API calls. 1 of those calls is dependent on what we can call the root call, such that if the root call is still resolving then we would need to wait for it to finish and return that data from the API before calling the dependent call. So:
Root call
Dependent call
Call 3
This is a React/Redux project. I could track which calls are in the process of "submitting" in a redux store and if the root call is "submitting" I could setup a setInterval to check if it's resolved before firing off the dependent call. Since this file is outside of a react component, to implement the aforementioned method, I would have to import the store and depend on the store.getState() method to check the state of the store which may have just been changed in a previous if block when the action called within it sends an update to the reducer. Would calling getState directly after firing off an async action reflect the correctly updated state?
As an alternative, what if I stored the most recent call in a global variable in the api file, and then naively chained a callback to it, like this:
api.js
let currentRootCall = Promise.resolve(null);
function setCurrentRootCall(call) {
currentRootCall = call;
}
export default function submit(values){
...
if (willSubmitRootCall) {
rootCall = doRootCall(values);
setCurrentRootCall(rootCall);
} else {
rootCall = currentRootCall;
}
...
if (willSubmitDependentCall) {
rootCall.then(() => doDependentCall(values));
}
if (willSubmit3rdCall) {
thirdCall = do3rdCall(values);
} else {
thirdCall = Promise.resolve(null);
}
...
return Promise.allSettled([rootCall, thirdCall])
.then(...) // fire off call completed actions for each of the 3 calls in the redux stores
}
This would save me the headache of polling the redux store to see if the call completed and then having to clean up the interval. It is my understanding that chaining to a resolved promise will just immediately fire off the chained callback. I feel as though I'm missing something here.
4 questions:
Is there a problem with this Promise chaining approach?
Will naively chaining to a possibly already resolved Promise cause
problems? How about with Promise.allSettled?
In the polling redux store method, would calling getState on the store directly after calling an async action reflect the updated state?
Is there a better way?
FYI: I am using axios library to make these calls.

Changing the state with returned JSON data

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

Categories

Resources