When I learn React, everyone tells me setState is async because if I console.log(state) right after setState, it will print the old value.
However, when I check the source code, useState doesn't return any promise nor use async/await.
Meaning setState must be sync. But if it's sync then why React doesn't re-render the component right away when I call setState? As you can see in the code snippet below, 0 gets printed first, then render1 is printed later.
If this is because of batching then how does batching work? How is it possible to "wait" until specific time to trigger the re-render of App component. And even more important, how does React know that it needs to call App in this case and not other functions?
export default function App(){
const [count, setCount] = useState(0);
console.log("render", count);
return <button onClick={() => {
setCount(count+1); // why doesn't App get called/re-rendered right away here
console.log(count); // this prints the old value first then re-render happens later
}}>
</button>
}
First, I would caution you that the reason why console.log(count) logs out the old value has nothing to do with sync vs async. The reason why you log out the old value is that count is a local const which will never change. It started its existence as the old value, and it will always be the old value no matter how much time passes, and no matter the order of operations.
Calling setCount does not change the value in count, it just asks react to rerender the component. When that new render happens, a new set of local variables will be created, with the new values. But your console.log from the previous render can't interact with values in the next render.
That said, it is true that rendering in react is (usually) delayed briefly to batch up multiple changes. The exact way they do this i'm not sure, because there's several possibilities. I've previously looked through their code, but enough has changed in the last few years that i doubt what i found will still be useful. In any event, ways that they could batch the rerender include:
setTimeout. The first time you set a state, they could set a timeout to go off soon. If another set state happens, they take note of the state, but don't bother setting an additional timeout. A little later, the timeout will go off and the rendering happens.
A microtask. The easiest way to do this is Promise.resolve().then(/* insert code here */). Once the current call stack finishes executing, microtasks will run. This approach has the benefit that it can happen sooner than a setTimeout, since timeouts have a minimum duration
If execution began inside react, they could just wait until your code returns, then do the render. This used to be the main way react batched changes. Eg, they had a piece of code responsible for calling your useEffects. During that, it would take note of state changes, but not rerender yet. Eventually you return, and since returning puts code execution back in react's hands, it then checks which states have changed and rerenders if needed.
how does React know that it needs to call App in this case and not other functions?
It knows that because the state that you changed is a state of App. React knows the tree structure of your components, so it knows it only needs to render the component where you set state, plus its children.
Related
I'm working on a Stencil JS project, that makes some network calls to get data and update state.
In React JS, a network call would be done in componentDidMount lifecycle method, and not in componentWillMount method.
I'm surprised to find almost the opposite recommendation in the Stencil docs:
componentWillLoad() Called once just after the component is first connected to the DOM. Since this method is only called once, it's a
good place to load data asynchronously.
Stencil favors componentWillLoad over componentDidLoad in another case too. It logs out console warnings when using componentDidLoad to update state:
STENCIL: The state/prop "exampleProp" changed during "componentDidLoad()", this triggers extra re-renders, try to setup on
"componentWillLoad()"
Why does Stencil push users to componentWillLoad method (before render), while React pushes users to componentDidMount method (after render)?
To start off I don't know much about React but I saw that componentWillMount was deprecated with the following explanation.
There is a common misconception that fetching in componentWillMount lets you avoid the first empty rendering state. In practice this was never true because React has always executed render immediately after componentWillMount. If the data is not available by the time componentWillMount fires, the first render will still show a loading state regardless of where you initiate the fetch. This is why moving the fetch to componentDidMount has no perceptible effect in the vast majority of cases.
In Stencil you can return a Promise in componentWillLoad which will prevent the component from rendering until that Promise has resolved (but I never found a practical use-case for this).
If you don't return a Promise then depending on the speed of the your render method (any possibly other render lifecycle methods) componentDidLoad will run slightly later, which will probably be minimal most of the time (i.e. "no perceptible effect") but without any advantage (that I can think of).
componentDidLoad is meant for things that have to run right after the first render.
Similarly the console warning is because most of the time it doesn't make sense to synchronously modify a #State() or #Prop() property in componentDidLoad as it can often be easily moved to an earlier lifecycle method (avoiding a re-render just after the initial render). There are exceptions though, e.g. if the state value depends on the generated DOM, in that case you can ignore the warning.
Note that in some cases connectedCallback might be even more appropriate than componentWillLoad e.g. the clock example in Stencil's Life Cycle Methods docs.
I'm looking for information about whether promises chains are guaranteed to be completed before being garbage collected if the reference is lost. I'm executing a call to my API within a React useEffect hook, but am not clear about what happens if one of the dependencies change and the useEffect hook is re-executed while the promise is still pending.
A contrived example:
const useApiFetch = (query, myVariable) => {
const client = useClient();
useEffect(()=>{
client
.query(query)
.then(() => console.log('inside thenable'))
.catch(() => console.log('inside catch');
},[variable, client]);
}
Questions:
What happens if myVariable changes and useEffect is re-executed while the promise is pending? Will the promise complete before being garbage collected?
What about if the component that is consuming this hook is re-rendered or removed from the virtual DOM?
If I don't have a cleanup function, is there any chance of a memory leak?
I may not be the perfect one but from my usage with react and functional components i will try to answer these.
When your dependencies for the hook changes, the function inside is re invoked. That means in your case a second api call. What this in turn results in may depend on api response time, client device, internet speed and way you handle these.
For example, if you are rendering something in the promise success for example a Text. What happens is once you UI renders the first data that it gets, like if you are setting state or something. Aa soon as the next api call resolves the UI again re-renders to reflect that change.
taking your example code. If you change the dependency "variable" three times, you get three "inside thenable" if promise resolves or else the catch console.log.
Note: If for any reason you api process takes some time for example a large query, then you may not be able to tell which api call will get resolved first. I had this issue when i implemented a text based onChange search using api. A good solution will be to debounce your api calls to limit no of calls and also to cancel unwanted calls.
If you have setStates that is tied to your promise resolution/rejection and you haven't properly handled the unmount condition. React will show you a warning stating the same. Something that means "You have a state change happening in an unmounted component/screen". This can lead to memory leaks if left unchecked.
Hope you get some point out of this. I am not a pro at this, these are somethings that i found while working with React.
I'd like to run a function upon receiving props initially and upon any subsequent props changes. Consequently, I was planning on checking props and running this function in both componentDidMount and componentWillReceiveProps. However, this seems redundant. I've seen people also check props and run functions inside render(), but I was under the impression this was frowned upon.
What should be my solution here?
There is no substitute to catch both componentDidMount and componentWillReceiveProps in a single callback. The best way to do this is to define a function and call it from both of these callbacks.
I was planning on checking props and running this function
Is a very vague description of what you're trying to do. Just checking the props alone doesn't seem to be problematic if done inside the render function. But it would become a problem if this part is causing side effects which (can) in turn trigger a component re-render. If this is the case, I'd say "frowned upon" is putting it lightly. The problem with this is that your render function is adding side effects which are in turn triggering more re-renders and on it goes. It will instantly kill the performance of your app and could even make the whole thing grind to a halt.
Your question is tagged with both React and Redux. Are you using a mapStateToProps function that you use with connect?
If so, a possible answer could be to dispatch this action (or run this function) in the mapStateToProps function, since this will be run both initially, and then every time you update the redux state. You'd have to make sure the action/function doesn't cause yet another state change though, so that you end up in a loop.
Edit: #DuncanThacker pointed out this might not be a good idea, because mapStateToProps may fire very often, resulting in performance issues. Good point.
It is my understanding that you are not supposed to change states in the render function cause that would cause and infinite re render or the component.
This makes perfect sense, but I find myself in a particular situation. I am building an offline application and I am using an offline storage system to retrieve data. Now, whenever a method is called to get certain data, cache is checked, if it is not expired the component will be able to access the data and therefore nothing happens, but if it is expired there is a call to the API, the data is updated and the interested components re-rendered.
These methods will change the state of the component the first time they are called because they are going to the API grabbing the new data and re-rendering, and then they will not change the state anymore because the data will already be in cache.
Now, I could call these methods in component will mount, and that is what I am doing now, but if I am forced to re call them, I need to unmount and remount the components. Is this the only possible way of doing this?
Thanks!
Well the first step is understanding that state management and rendering needs to be decoupled which you got already.
Now what you can do is consider your external state/cache element as an observable object (ie. I want to do something like observableObject.listen('change', onChangeHandler); you can use EventsEmitter from the events library). You do that listening on componentDidMount and clean up in componentWillUnmout. That onChangeHandler is very simple: this.setState({ value: observableObject.value }) which will trigger a component re-render which should be a pure function that outputs DOM nodes depending on props being passed and it's own state.
This means that your logic of checking if the cache is invalid is not on a per request of the value (inside rendering) but rather think of the object as self contained. It regularly checks if itself needs to notify its listeners that it changed. Since JS does not do parallel execution you don't have to deal with threads and synchronization. You know that at the point in time your render function executes it will have the latest value. If after rendering the logic that checks for cache executes and it sees that it needs to be updated it simply notifies as said earlier its listeners and that makes your component re-render because your onChangeHandler changed the state.
Hope I helped.
If you do an asynchronous action that updates the state in componentWillMount (like the docs say), but the component is unmounted (the user navigates away) before that async call is complete, you end up with the async callback trying to set the state on a now unmounted component, and an
"Invariant Violation: replaceState(...): Can only update a mounted or
mounting component."
error.
What's the best way around this?
Thanks.
You can use component.isMounted method to check if component was actually attached to the DOM before replacing its state. Docs.
isMounted() returns true if the component is rendered into the DOM,
false otherwise. You can use this method to guard asynchronous calls
to setState() or forceUpdate().
UPD: Before you downvote. This answer was given 2 freaking years ago. And it was the way to do stuff back that days. If you are just starting to use React do not follow this answer. Use componentDidMount or whatever another lifecycle hook you need.
isMounted() is actually an easy way to solve most problems, however, I don't think this is an ideal solution for concurrency issues.
Now imagine that the user clicks very fastly on many buttons, or maybe he has a very poor mobile connection.
It may happen that finally 2 concurrent requests are pending, and on completion will update the state.
If you fire request 1 and then request 2, then you would expect the result of request 2 to be added to your state.
Now imagine for some reason request 2 ends up before request 1, this will probably make your app unconsistent because it will show request2 results and then request 1, while your last "interest" was actually in request 1 answer.
To solve this kind of issue, you should rather use some kind of Compare And Swap algorithm. Basically, this means before issuing the request, you put some object node in state, and on request completion, you compare with reference equality if the node to swap is still be node you are interested in when the request completes.
Something like this:
var self = this;
var resultNode = {};
this.setState({result: resultNode});
this.getResult().then(function(someResult) {
if ( self.state.result === resultNode ) {
self.setState({result: someResult})
}
}):
With something like that, you will not have concurrency issue in case the user clicks to fast on the buttons leading to current requests inside the same component.
update 2016
Don't start using isMounted because it will be removed from React, see the docs.
Probably the best solution for problems arising from async call from cmomponentWillMount is to move things to componentDidMount.
More info about how to properly get around this problem and how not to need to use isMounted here: isMounted is an Antipattern