React’s setState is a special function, and it helps with asynchronous concurrency
So how do we use it properly?
Let’s investigate this code example here:
componentDidMount() {
fetch('http://example.com/rss.xml')
.then((response) => response.text())
.then((response) => {
parseString(response, function (err, result) {
this.setState({
articles: JSON.stringify(result.rss.channel[0].item)
})
console.log('RAW: ' + result.rss.channel[0].item);
console.log('THIS: ' + this.state.articles);
});
});
}
First, notice the code is in the componentDidMount()
lifecycle method. The component was just born, and now it is making a fetch()
call to get some data from a server.
Bonus Note: Notice how it is shown in
componentDidMount
and notcomponentWillMount
. This is because network calls should be made here. If you make a network call in componentWillMount, there is a chance you could receive the data before the component is fully mounted. At which pointthis.setState()
wouldn’t work because there is no component on which to set.
The important thing to note is that once the data is acquired, it is set into the component’s state via this.setState({ articles })
.
In React, setState is asynchronous, so the code on the following line after setState (the two console.log()s
) will be executed immediately after the setState call is placed in the event loop. This is a problem.
The above code is flawed because those two console.logs are being executed before setState has completed. Great, now we’re screwed. Well actually, it’s fine.
If you want to read closer about this moment in time of setState, check out the top answer here:
It doesn’t go into hyper-fine grain detail, but that’s ok. If you are at the point where you want to know exactly, you should scour the React documentation here:
setState Callback
The secret trick here is that setState accepts a second parameter which is a callback that will be executed after the state is updated. You could place all your following logic inside that statement.
Here is a sampler pack:
this.setState({ dogs: 350 }, () => { console.log('The state has been updated.') })
The second parameter to
this.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.
Notice how it says ‘once setState is completed’. Another way to state that is to say, once the state change has been resolved. It implies a formal change process and definitely not a synchronous function doing one set
operation.
Let’s say for a second you had several components in React and each one calls this.setState()
at the same time. You would probably consider it very important to know the exact order those calls were made if they were all incrementing the same state variable.
React can batch and reconcile those crazy situations for you and help maximize performance. The important thing is FIFO (first in, first out). React is considering your state changes with respect to time.
Back to our first example code at the top, it could look like this:
componentDidMount() {
fetch('http://example.com/rss.xml')
.then((response) => response.text())
.then((response) => parseString(response, (err, result) =>
this.setState({
articles: JSON.stringify(result.rss.channel[0].item),
}, () => {
console.log('RAW:', result.rss.channel[0].item)
console.log('THIS:', this.state.articles)
}));
);
}
Basically React is doing some wizard techniques behind the scenes to ensure your state integrity is maintained when you want to see what the state is after the change is complete.
I can see why such complexity seems a bit bulky, but the process is not intended to be as simple as possible. this.setState()
is intended to provide a formal change management process because state is the most important thing at the moment of change. Outcomes depend on order of operations, otherwise math doesn’t work, and you see bugs and poor UX.
Check out the modification I did on the console.log:
// before
console.log('THIS: ' + this.state.articles);// after
console.log('THIS:', this.state.articles);
It too can accept multiple parameters.
Asynchronous Concurrency
Read this definition for console.log()
and multiple parameters.
[Console.log accepts: a] list of JavaScript objects to output. The string representations of each of these objects are appended together in the order listed and output. Please be warned that if you log objects in the latest versions of Chrome and Firefox what you get logged on the console is a reference to the object, which is not necessarily the ‘value’ of the object at the moment in time you call console.log(), but it is the value of the object at the moment you click it open.
Cite: https://developer.mozilla.org/en-US/docs/Web/API/Console/log
This definition is incredibly important because it means what you see in your Dev Tools console as output from console.log()
does not necessarily reflect exactly how the Objects looked at the moment they were logged. Live references are the reason, meaning pass by reference not pass by value. A good way to facilitate that (which is related to time travel debugging FYI) is to use immutable Functional Programming techniques. You have to make copies. Copies are basically snapshots with respect to time, so you can benefit from using ways of thinking related to Calculus, or the study of continuous motion. Of what? Data flow with respect to time.
this.setState()
is a helper that only gets in your way enough to help promote state integrity and efficient data flow. When? During asynchronous concurrency centered around data flow while not losing track of any data. Economics depends on it.
I like that console.log()
definition additionally because it speaks to the asynchronous nature of live references. One function by itself can be synchronous, but due to the callstack and function queue, you can load up an infinite number of functions into the queue and they will complete in a random order based on how long each one takes to complete because only one function passes through the callstack at a time, on the main thread.
Of course, it seems random order to us, but it’s actually the mathematically-exact fastest path through all those functions, assuming they are all deterministic.
Fast forward to our fetch()
example, setState doesn’t care to stop surrounding code from executing or wait for surrounding code unless you explicitly tell it to. That’s what the callback is for, if you need to run some bonus code.
Functional setState
While we are talking about setState
, I should mention also that you can pass a function to it. Imagine that the second parameter callback is your method of looking into the future after setState. The opposite of that is looking into the past, which is where the functional setState comes in handy by giving you closure around the previous unit of time. The previous state also happens to be the current state of whatever you are updating.
Functional setState is great because you are guaranteed first in, first out in the order functions are called. You should research that further. You may find this article here particularly revealing:
Here is a sampler pack for that:
this.setState((prevState) => {
// hello I like cats.gif
// we could run some fascinating code here
// as long as we return an object
console.log('rad') return {
articles: [ ...prevState.articles, { new: 1, article: true }],
}
})
Functional setState
gives you a safe window to guarantee state integrity through your update. I showed that example as spreading an Array into a new Array and appending it with an object to demonstrate a real scenario where you might need to refer to the current state as part of your setState operation.
In a real scenario, you might sharpen that above code to this, which capitalizes on implicit return of an Object literal (requires fat arrow syntax):
this.setState(prevState => ({
articles: [ ...prevState.articles, { new: 1, article: true }],
})
Hopefully that helps us see the climate of what is happening. In React, it is important to undergo a formal change management process, so every time you are getting or setting data, you need to be careful who is reading or writing data and from where, like which functions and which part of your application.
React’s way of taming JavaScript is to promote data to flow unidirectionally, immutably, and deterministic.
Reactive Programming
It makes things easier to reason about
if everything is flowing one way. That only works if you require immutability and prefer a deterministic system. It means most functions are written declaratively, so they declare what the state looks like at the start of a function, do stuff, then declare what the state is at the end of the function.
React makes you think you are writing mostly pure JavaScript, but really it is managing your state using a first in, first out technique to avoid race conditions when perhaps thousands of components are trying to write to the state at the same time. All while the user is in the browser rolling their face across the keyboard triggering all kinds of events.
We must not block the main thread or else suffer poor UX. It’s important we circle back on that idea because mostly what we are talking about today is how. It’s more important what we are doing — trying to maximize efficiency of managing actions and events while maintaining the app’s state integrity.
Imagine you had a calculator, like the one you used in school, and it randomly gave you the incorrect answer. Not only is that a pretty garbage calculator but also it lost control over its state. The calculator only had one job. React is doing that job for you, and setState
helps.
A formal change management process means there is probably an official pattern that you should use every time you get or set data. Luckily, the patterns are usually what you would do if you were writing pure JavaScript. Reactive Programming, Functional Programming, and immutability can help tame the wild asynchronous parallel concurrency gods. If you haven’t came across the word semaphore
yet, then you should read this definition:
Reading the definition of ACID Transactions
can be beneficial as well:
A transaction is like a state change. In fact, I’m pretty sure it is exactly a state change. For example, completing the transaction of giving someone $3.50 has an affect on this.state.yourWallet
and this.state.theirWallet
. Sometimes databases lock over the period of a transaction. It’s called awrite-lock
. This creates a situation where state changes are getting backed up. This is called dead-lock
. You want to stay away from deadlock.
The nice thing is, immutable Functional JavaScript is fairly ideally-suited to handle the continuous motion of data flow. It’s single threaded, but it’s also replicable. A cluster of JavaScript nodes is an extremely beautiful thing to examine. It would look like a recurrent directed acyclic graph. That is to say, it would look like a directed acyclic graph that can be fed back into itself. If that’s not mind blowing, I don’t know what is.
Note about DAGs:
Adding the red edges to the blue directed acyclic graph produces another DAG, the transitive closure of the blue graph. For each red or blue edge uv, v is reachable from u: there exists a blue path starting at u and ending at v.
DAGs are related to function composition, because a function has a discrete start and discrete end, so it is directed, and it does not loop back to itself, so it it unidirectional. Acyclic means “not cyclic” which means “not occurring in cycles” which means “not circular” which means “straight line”.
A deterministic chain of functions is a directed acyclic graph because each function can bifurcate which creates a giant tree but it does not loop back into it self, so it allows you to maintain a deterministic movement when you use recursion, meaning it still always outputs the same thing if you put in the same thing.
In a DAG, you do not go backwards but you may pass through recursive functions. I’ve never seen anyone talk about this stuff, but it reminds me of Mathematics, so I describe it this way. Imagine a fractal.
My DAG mentions are not directly related to this.setState()
, but they are indirectly related because most React apps are DAGs. They have a root component, and data passes from there to be rendered on your screen and may enter your app somewhere downstream.
We also recall that data typically flows unidirectionally and this gives rise to the React framework’s setState method because it is designed to facilitate first in, first out processing and reliable order of operations.
As such, when you want to set a component’s state, it’s really an event. You are passing that event either a function or a plain old JavaScript Object (POJO), and you can additionally supply a callback function if you want. It will be processed after.
TLDR
It’s very important what you are doing before, during, and after this.setState()
. It's a special function, a class method on the Component Class. I hope I have helped us understand a couple of its secrets today.
Our fetch()
example was seeking to perform two operations in one setState call. Normally, you only do one which is to set the state ☺. Normally, other components are listening for changes, so setState callback is a bit more imperative than it is declarative; however, sometimes it is useful. Using setState’s callback does nest one additional dimension, but it’s fine because we are just performing one more operation, or a couple quick ones. I wouldn’t recommend it if you want a chain of functions in the callback.
Notice the React documentation that states,
Generally we recommend using componentDidUpdate() for such logic instead.
The reason it says that is componentDidUpdate()
is listening for state changes, so you can run logic there that is listening for certain conditions and then acting upon those changes. It saves you from having to care about performing a second operation after setState at the call site (ie: where in the code you called this.setState()
).
Imagine you did this instead for our fetch()
example:
componentDidMount() {
fetch('http://example.com/rss.xml')
.then((response) => response.text())
.then((response) => parseString(response, (err, res) =>
this.setState({
articles: JSON.stringify(res.rss.channel[0].item),
hasFetchedStuff: true,
})
));
}
Check out that hasFetchedStuff
piece. That could lead to something like this in componentDidUpdate()
:
componentDidUpdate() {
if (this.state.hasFetchedStuff) {
this.triggerSomething()
}
}
That can free your componentDidMount from having to care about anything after getting the data, which is perhaps good decoupling and a nice separation of concerns.
It’s also more reactive-like because:
- when the mount event occurs, do this
- when the data is acquired, do this
Later, you can freely change how the data is acquired or what you do with it just by manipulating those hook points. The start of a function always presents a window of opportunity to see what it is allowed to, and the end of a function always presents an opportunity to see what was happening at that moment in time somewhere in the application.
I wrote this originally as an answer on StackOverflow, but I realized it was kind of long and should be an article in itself.
You could follow me on Twitter. I use it to render key thoughts: