Ancient City Ruby 2019

Functioning in React: A Deep-Dive into useState()

Ancient City Ruby 2019

00:00:12.790 Okay, hey! Good afternoon everyone, it's great to be here. I like to think of technology as a bit like a Ponzi scheme, and I will back this up with a quote from Malcolm Gladwell about his father.
00:00:21.770 If he doesn't understand something, he just asks. He doesn't care if he sounds foolish. He will ask the most obvious question without any concern. He asks lots and lots of dumb, in the best sense of that word, questions. He'll say to someone, "I don't understand, explain that to me," and he'll just keep asking questions until he gets it right.
00:00:45.380 Gladwell argued that his father would never have fallen prey to the famous Bernie Madoff Ponzi scheme because when presented with the idea and the investment documents, he would have said again and again, "I don't understand, explain that to me." Because he couldn't understand it, he wouldn't have invested any money.
00:01:07.130 I think that this curiosity is represented in our field by a spectrum. On the left, we have the hares, these are the developers who say, "I don't care how this language feature works, I'm going to use it and ship it," much to the dismay of the tortoises on the right. On the right are the tortoises, the Malcolm Gladwell's father types, who say, "I want to know more about that, explain it to me," much to the dismay of the hares.
00:01:29.420 Both kinds of programmers build amazing things. I lean pretty far towards the Gladwell side. Maybe you have a person like that on your team; they spend a lot of time saying, "Whoa, whoa, whoa!" They like to break things down, draw on a whiteboard, and write documentation.
00:02:00.950 So, hello! I am Jake. I am involved in the web community and work at Hashrocket in Chicago. For the last five years, I've been working on a site called Today I, which is a daily record of my team's learnings. I also write for jQuery.com.
00:02:23.060 Returning to the spectrum I mentioned, it maps to an idea I call the Tower of Abstractions. This is something Steve Jobs talked about many times: the idea that we are all building on the twenty-thirty or four-hundredth floor of a tower. All the work that our predecessors have done serves as the floors beneath us. Sometimes their conceptual floors are ideas, and sometimes they are physical floors like a microprocessor.
00:02:51.460 Having these floors gives us an incredible power to innovate, and we are meant to ignore the floors beneath us. I think this is a genuine way to look at software, but I also believe there is room in technology for people like me.
00:03:00.950 People who see all these floors and start asking questions about them, continuously saying, "I want to know more about that" until they get it right. One floor I've been pondering for over a year is React hooks, specifically the state hook.
00:03:34.370 This is the state hook, useState. The prefix 'use' is mandatory for any React hook and is followed by 'state,' which represents the value you get and the thing you can control. It seems pretty simple, but I think it's probably not so simple on a lower level of the tower, and that's what I want to discuss today.
00:03:55.519 I'll start off with a question: who is this talk for? React developers, obviously. Raise your hand if you have used React. Great! I'm guessing with that number and the interest in React, we've got most of the room. So that's all of you, and nobody can leave! Whether you're new or seasoned, I'm really glad you're all here.
00:04:11.150 Apart from JavaScript, I hope this talk appeals to people who just want to understand how something works. I have a disclaimer as well: you don't need to know any of this to use hooks. However, I hope that by the end, you'll be glad you stuck with me.
00:04:39.160 Back to the thing I want to know about—the state hook. I have a couple of questions: What is state? What is state to React? And why is the way we manage state suddenly changing? This is my pitch: understanding this hook, useState, will make us better developers.
00:05:11.150 I'll make this argument through the following agenda: Part One - A history of state, where I'll offer a short history of state in computer science and React; Part Two - The state hook's deep dive, where we'll explore the React codebase and try to figure out how this hook works; and Part Three - Hooks in my code, where I'll share some thoughts on hooks as of this particular moment, October 2019.
00:05:35.570 This talk consists of 66 slides and will last about 35 minutes. So, let's dive into Part One - A history of state. In this part of the talk, I want to discuss state from two perspectives: state in computer science and state in React.
00:06:05.659 Let’s pretend we're in CS 101 and ask the question, what is state? I found a definition online that I think is quite good: 'Stateful' means the computer or program keeps track of the state of interaction, usually by setting values in a storage field designated for that purpose. 'Stateless' means there is no record of previous interactions, and each interaction request must be handled based entirely on the information that comes with it.
00:06:39.830 So 'stateful' keeps track while 'stateless' has no record. It conjures into existence this concept called state, which is the collection of the conditions of a given environment, and it's the absence of those conditions that makes something stateless.
00:07:05.520 An example of a stateless interaction is HTTP. When I request a web page, it's returned, but the web server is not set up to remember those requests, so each piece of information I receive is discrete and unrelated to the others. An example of a stateful interaction is a browser using local storage to record some information about me, like how many times I've viewed a page on hashrocket.com/blog.
00:07:46.570 State is such a common goal in programming that we developed imperative programming languages in part to manage it. So, what is an imperative programming language? In computer science, imperative programming is a programming paradigm that uses statements to change a program’s state.
00:08:06.300 These imperative programming languages know about and are able to control and change state. In client-server interactions, state is so important we have this complex architecture, including the browser, storage fields, and languages to support it.
00:08:39.830 JavaScript has state, and therefore, so does React. The syntax for React state has changed over time, but I think it has been very consistent.
00:08:50.800 The following patterns, at least, have been supported: the ES6 module pattern, ES6 component classes, stateless functional components, the class field syntax, and stateful functional components, also known as hooks.
00:09:13.840 I'm going to piece together this history from various release notes and legacy documentation. I'll point out that all the code I'm discussing here still works; I've loaded it into a single React 16.8 code demo, and it still works today.
00:09:38.159 We’ll start with React 0.13, which was the penultimate React release before moving into the major releases of React 15. React 0.13 supported an older JavaScript syntax called the ES3 module pattern.
00:10:00.000 Here's our PirateShip component. The key element we will keep track of for the rest of the talk is 'cannonsCount', the number of cannons the pirate ship has. We start with a function called PirateShip that takes initial props as an argument. It returns an object with two keys: 'state' and 'render.' The 'state' key's value is an object where each key represents a slice of state, and its value is its initial state value. So cannonsCount starts from zero.
00:10:39.360 Then we have our 'render' key, which has a value that is a function returning JSX, able to introspect about state. One of the changelogs I found while preparing this research stated that this way of defining state would not be supported and in fact would be deprecated in React, but it still works in the code sandbox I showed.
00:11:29.990 React 0.13 also introduced ES6 style class components. Here is our Class PirateShip that extends from React.Component. We immediately define our constructor function, which takes props as an argument, and you have to call super(props). Then we set the state with this.state, which is also an object, again pointing to its initial state value.
00:11:57.000 We are still returning JSX that looks at this state, but this time it's inside a function called render.
00:12:04.540 A fascinating blog post preparing for this talk was by Dan Abramov, where he discusses what super(props) is all about. I'll link that at the end. This is a stateful class component as we recognize it today. When we want to change the state, we call setState.
00:12:42.610 There are two supported syntaxes: the first is the updater function syntax. With the updater function, you have access to the previous state and props, allowing for a more accurate update. The second syntax allows you to pass an object as the first argument to the function.
00:13:05.740 React 14 introduced stateless functional components. To quote the release notes from that time, in idiomatic React code, most components you write will be stateless. This hints at where React ended up with hooks.
00:13:30.240 Here is a stateless functional component. You can tell by the casing it takes an argument called props, and then we can do whatever we want with the props. Here we're just extracting cannonsCount off of the props and inspecting it in the JSX return.
00:14:10.290 I previously loved taking stateless class components and refactoring them into functional components like these. However, I see a problem: props control everything. These components are dumb and thus less featureful than stateful class components, without any particular reason.
00:14:31.580 If you choose to write this way, you don't have state. Now that we're in the realm of ES6, we can refactor stateful components to use the class field syntax. This component is nearly identical to the previous class component, except in its setting of state, we do it in a class field object.
00:15:07.690 We set the state and point it to our object without the need for an explicit constructor function. We don't have to call super(props), and we can access this.props inside the object. This is how the world appeared until February 2019, when a major shift occurred.
00:15:41.760 Specifically relevant to this talk was the release of the state hook. The state hook is described in React RFC 68 and was introduced in React 16.8. Hooks were described as a primitive for sharing stateful logic without writing a class.
00:16:02.800 Inside the PirateShip function, we are calling useState on the third line. The useState function accepts one argument, which is the initial value of state, and it returns an array containing two elements: the first is a variable pointing to our slice of state, and the second is a setter function for updating just that slice.
00:16:29.120 You can use useState more than once, just stack them. If you have multiple slices of state, this is the preferred way of doing it. So now, what have we learned so far? We've explored some state history and theory, and we've discussed different ways that React has supported statefulness and statelessness.
00:17:05.280 However, looking at this code, I still have a question: how can a function have state? That's the question I'll tackle in the second part of the talk.
00:17:36.170 Okay, now onto Part Two: A state hook's deep dive. Water break!
00:17:50.300 In this part of the talk, I want to try something—a deep dive into the React codebase to address the challenging question I posed: what is going on here?
00:18:14.130 I believe it will be a fun opportunity for us to learn more about React and for me to read a lot of source code for a project that I didn't write. Some stuff here is my interpretation. I'm not confident I will get every detail right, but that’s okay! Diving into waters like these every so often is good for the soul.
00:18:36.445 On this dive, we'll encounter multiple packages, type safety, dependency injection, interesting comments, and leaps of faith. So, let's get into the codebase! As I do that, I'll point out that this stuff changes a lot.
00:19:00.600 This is the head of the React codebase that we'll be examining, which I pulled in early September. Now that everyone has that committed to memory, we will move on. The code I showed today has been heavily edited because it tends to be very noisy, and I wanted it to fit on the slides. How do we get into the React codebase?
00:19:41.660 We start by calling useState. We understand what this function is, what its argument is, and what it returns, but where does useState come from? I left out a bit from the example before.
00:20:01.260 So we're importing this capital 'R' React and a function called useState from React, which gets us into the React codebase. The code from here on is going to be React source code. But what is React?
00:20:26.370 In React 14, something called the package split happened. From that moment on, React has been at least two things: React itself and React-dom. When that split happened, React became the package that exposes APIs. Most of the implementation lives in the renderers, with React-dom being one of them.
00:20:51.180 We will dive into a renderer; we have to wait and see. Inside the React package, we find a file called index.js. In true JavaScript idiom, we read from bottom to top and export either React.default or React, allowing for different ways of importing a default.
00:21:22.530 Someone left a helpful comment stating that this is hacky but makes it work with both Rollup and Jest. And React's open-source rockstars are just like us! We’re getting React from a place called source react, which is where we should expect to find useState.
00:21:54.740 Let's dive into source react and try to find our useState. Reading from bottom to top, we export a default object called React. If we scroll up a bit, we see that we define React and it's filled with ES6 object literals, one of which is useState and there are many others.
00:22:16.790 This is a massive object exposing all of React. I've omitted many keys, including the gem that says 'secret internals, do not use or you will be fired.' Good to know, but you can't fire me from this talk!
00:22:41.940 So going back, we are importing a function called useState from this place called react-hooks. It seems we're getting very close to finding the real useState.
00:23:03.600 All right! Here it is! We found useState. You might notice some different syntax here: this function is type-safe via Flow. For those interested, I'll walk through that quickly because I think it reveals something about this function.
00:23:30.300 We declare useState followed by a Flow function type annotation. The first element in that annotation is a less than capital 'S' greater than in angle brackets, called a generic. It's not just limited to Flow—it represents a way of capturing the type we want state to be for various types.
00:24:01.290 Instead of just saying it can be any type, we want to capture what that type is so we can reference it later in the definition. You see the 'S' later on in the declaration, which provides polymorphism for our type system.
00:24:24.670 Then we have the initial state argument, which can either be a function returning type 'S' or just type 'S'. Our initial state in this example is cannonsCount, which will be set as zero.
00:24:58.810 You might think, 'I'm not really interested in Flow or don't really understand it.' The source dive must stop here. Well, I didn’t know Flow when I started reading this code, but I pushed on. I found Flow’s try flow page just for validating your flow code.
00:25:34.510 The page provides a Flow Abstract Syntax Tree (AST). If you haven’t encountered an AST before, they can be tricky to read, but the answer to the question, 'What is this Flow code doing?' can often be found in the AST.
00:26:10.419 To the right, I’ve highlighted a generic type annotation from the previous slide that maps up with the 'S' in line one right before the closing parenthesis. That’s how I was able to get past this initial confusion.
00:26:39.450 Returning to the useState function, it seems like we’ve found our useState—or have we? Moving into the function, we see a call to a resolved dispatcher, and the return from that call is what we are extracting.
00:27:09.190 We’re injecting functionality from another place and exposing it here free from all its implementation details. The React package gives us the features, but it doesn’t know how they work. To quote Dan Abramov, ‘the base class setState implementation has been an illusion all along. It doesn’t do anything; it simply forwards the call to the current renderer.’ The useState hook does exactly the same.
00:27:34.300 In this post-hooks world, stateful functional components acquire something called a dispatcher, which is the thing that implements useState. The useState function is an illusion—the cake is a lie! Let’s follow this resolved dispatcher to find the real useState.
00:27:54.620 Here’s the resolved dispatcher function. It’s a bit more complex because we are calling React current dispatcher.current, which is what we return. This dependency, 'react.current.dispatcher,' was formerly called React current owner, shedding light on what it does.
00:28:18.670 We need to check this React current dispatcher file. There’s not much to it. I think this is actually the entire file. Reading from the bottom up, we export a default for React current dispatcher, defined as an object.
00:28:34.500 It has a key current, with our Flow Union type indicating it can either be null or a dispatcher type imported from 'react-fiber-hooks.' I won’t discuss Fiber much today, but if you’ve heard of it before, I can say it’s a plain old JavaScript object data structure used to manage component reconciliation.
00:29:11.870 This appears to be a dead end; all that remains is importing some type. Thus, I’m unsure how to move ahead. However, there's one more interesting observation: you may notice that React current dispatcher is a constant. This can be a little misleading.
00:29:38.060 Despite its name, we know that, due to JavaScript's mutability rule, we can mutate that constant object. Here is our immutable object declared with 'const', giving it a key value that we don’t want updated; however, we can still use bracket notation to change it, meaning we can reassign constant object key values.
00:30:05.440 From this dead end, I’ll have to take a leap. For useState to function, it cannot be null. Yet there's nothing indicating when it becomes non-null. We’re four files in, and I still lack clarity on how this works. Help me, Dan Abramov!
00:30:32.550 Reviewing the earlier quote, the base class’s setState implementation has been an illusion—it doesn’t do anything except forward the call to the current renderer. Thus, we have to look into a renderer. Which renderer, you ask? Earlier in the talk, I mentioned several renderers, and I chose ReactDOM since that's where I spend most of my time.
00:31:09.600 We know that a renderer can change the current key. My theory is that to achieve that, it has to know about the current key, and there’s only one file in ReactDOM that knows about that: 'react-partial-renderer.'
00:31:44.250 Inside this file, we mention React and dispatcher twice. First, we set the dispatcher to something called 'react-shared-internals.react-current-dispatcher,' and that initializes it as null.
00:32:01.650 Later in the file, we set 'react-current-dispatcher.current' to something called 'dispatcher prior to rendering,' imported from 'react-partial-renderer-hooks.'
00:32:38.170 Thus, we need to visit that location. At the bottom of this file, we export a constant called 'dispatcher' of type dispatcher—many things reside in here, including useState, which comes from the same file.
00:33:15.060 This is it! This is the real useState. Skipping over the now-familiar type annotation, when we delve into the function, we notice it returns another hook.
00:33:36.740 The first argument represents a basic state reducer—an object that allows React to work with the useState hook and lets the initial value be a function. The second argument, initialState, is recognized as type any.
00:33:53.930 Rest assured, what we refer to here is not some global useReducer. This is a specialized instance defined particularly for ReactDOM. This function is pretty sizable, featuring an extensive if-else chain, consisting of at least 75 lines.
00:34:19.390 Below is a shorter overview of what that function accomplishes—at least four key tasks, likely even more, but these are the major ones I think are consequential. This function creates a work-in-progress hook using the function 'createWorkInProgressHook'.
00:34:41.760 Next, it sets a key on the return called 'memoizedState,' assigning it the state collected from input. Then it defines a 'dispatch' function and finally returns both the state and the dispatch function within an array.
00:35:05.630 The initial state becomes the work-in-progress hook, while the second, 'dispatch,' governs changes to state. These hooks are created by the function 'createWorkInProgressHook.' Here it is!
00:35:28.390 This function creates hooks, returning something of type hook, which is an object with three keys: memoizedState, queue, and next—all starting off as null.
00:35:49.440 This means the state is housed within this JavaScript object. ReactDOM reconciles by passing around these plain old JavaScript object values. The code from our component all the way back to the PirateShip component shows that cannonsCount and setCannonsCount can be regarded as the work-in-progress and dispatch.
00:36:16.650 As we come to the surface in our code, I intended to figure out what this hook does, and here’s my brief conclusion: React checks the types.
00:36:38.850 It builds an interface for a dispatcher. The renderer nullifies and sets a real dispatcher, and the dispatcher returns an array with memoizedState and a setter function, allowing for rendering and reconciliation.
00:37:03.890 Thus, your functional component state is passed around in an object within React. Next, let's move on to my final question, which is the shortest part of this talk: what does using hooks mean for my team?
00:37:24.070 Part Three - Hooks in my code. In this section, I want to take a moment to consider what hooks usage entails. If you adopt one hook, you open the door to all of them.
00:37:44.699 You cannot deliver a talk about hooks without sharing some strong opinions. Here are some of my firmly held, but weakly defended, opinions accumulated over a year of using them: Hooks are great, and you should use them.
00:38:10.060 Why do I believe this? First, hooks are ubiquitous. Raise your hand if you've seen hooks used either in production or in documentation/tutorials. That's a fair amount of the group. As a consultant, I've often worked with legacy code.
00:38:36.720 When that happens, clients experiment with these cutting-edge patterns, and we often witness the outcomes when they collaborate with us. Hooks were released less than a year ago, so my mental timeline indicates that I should be starting to see them soon in our clients' code.
00:39:05.600 The opportunity to ignore hooks is rapidly diminishing. The last React project I worked on launched in the same month that hooks were introduced, and although we wanted to implement hooks, the client wasn’t writing much new code then.
00:39:33.380 We were focused on deployment and ensuring everything was set, especially regarding SEO. However, I maintained contact with them, and now they're using hooks everywhere—again, that timeframe I described is swiftly closing.
00:40:03.310 Secondly, hooks address real problems. They aim to solve issues that have long troubled React. To name a few, one is setState: I view it as a persistent issue given that it treats state as one singular entity.
00:40:31.240 When you approach it this way, you tend to write updater functions burdening the entire piece. With the state hook, you can manage state using slices—much like Redux manages state—and I perceive this as a clean and scalable method.
00:40:56.370 Secondly, you can avoid writing constructor functions while still having state management. Writing constructor and super(props) feels a bit awkward. Additionally, the dual component syntax has never truly helped anyone learn React.
00:41:23.450 It's often led to many pointless refactors; hooks can potentially bridge that divide. Lastly, duplicated callbacks can be quite cumbersome; if you've ventured through a sizable React codebase, you likely know what I mean.
00:41:49.280 The useEffect hook is tailored to help remedy that issue. There are plenty more real problems, and hooks are crafted to tackle them, which I find to be commendable.
00:42:14.520 Moreover, state management is crucial. A fair amount of React code scrutinizes props, and the React core team intends for this approach to be standardized. However, props cannot encompass everything, and as much as I try to refactor away state when necessary, there's a significant aspect of JavaScript development concentrated on state management.
00:42:48.974 This situation explains the rise of JavaScript fatigue: hooks, context API, Flux, Redux, and MobX. That's all I'm going to say on that front. Yet, the importance of state management mandates we should reflect seriously on anything that strives to improve it.
00:43:10.270 Now, say you’re on board with my assertion that hooks are fantastic and you should utilize them. Should you rewrite your extensive React codebase to adopt hooks? Probably not. I've endeavored to convert some of my side projects to hooks.
00:43:34.030 In my experience, refactoring existing code to integrate hooks merely for the sake of that integration isn’t wise. Firstly, it will likely break something. Like any refactor, it’s critical to have good test coverage, something I often lack in my JavaScript side projects.
00:44:00.700 Secondly, you could incur version control churn; utilizing Git can muddle the refactor process, causing it to misinterpret the changes as new entities and losing all your line histories—quite significant change and stress for a refactor.
00:44:28.780 Finally, there’s the saying: if it’s not broken, don't fix it just to implement hooks. Nonetheless, I maintain that hooks are terrific and should be utilized!
00:44:51.090 Today, I discussed state, examined some code behind the state hook, and shared my thoughts. I posit that understanding the state hook makes us better developers, and I hope I’ve bolstered my case for delving into the source of your preferred JavaScript library. It's enjoyable and deepens appreciation for the tower of abstractions beneath us.
00:45:27.520 The individuals who build these frameworks are remarkable and very much human—they make decisions we can comprehend if we make the effort. I am incredibly passionate about React, and would love to engage further with anyone who shares that interest. Thank you all for listening!