React revolutionized the way most of us think about the state. No more binding, no more mess, just a beautiful unidirectional flow of data. It was great.
Until your app grew.
How the heck do you scale a central store of all app state? Where do you drill all the props? Why does every update go through a bazillion nested layers before it shows?
React Hooks enable a new architecture.
An architecture where state lives where you need it, business logic is where you use it, and anyone can understand your code without studying the whole codebase.
wizec is a prolific engineer, instructor, blogger, vlogger, conference speaker, and author. He's written many books including ReactForDataViz.com, Data Visualization with D3.js, the work-in-progress SeverlessHandbook.Dev, and ServerlessReact.Dev. He has trained the engineering teams from multiple Fortune 500 companies on React, Redux, GraphQL, Serverless, and other modern web technologies.
PUBLICATION PERMISSIONS: Reactive Conf Organizer provided Coding Tech with the permission to republish Reactive tech talks.
CREDITS: Original video source: https://www.youtube.com/watch?v=OJ7ZDfQ4snc
Hi, everyone, I'm Swiss or Swiss, that's depending on how Americanized you want to pronounce my name. And then I'm going to talk to you about a pattern, not a library. It's a pattern. I'm calling the wormhole state management pattern that I kind of developed when I was working on a library that some people are using. And I discovered this really cool pattern that lets you manage state in a more manageable way without using any to any dependencies, without state management, libraries, just using stuff that's already in rehab. So, oh, by the way, I don't have an interest line because I don't like those. If you want to know more about me or if you want to reach out. I think if you type Suzette's into Google, you're going to find a lot of stuff about me. So. I don't know how long you've all been VEAC developers, but you might remember back when we act first came on the scene, it made a huge splash with this uni directional state management system where you uni directional data flow, because back then this was, what, 2014, 2015? I don't have a mouse when I'm present. That's fine. So I can't open the chatroom. So back in 2015, 2016, somewhere thereabouts. The way we used to build these Web apps was that you had a lot of painful pain with doing state management. You could either use an event based system like backboned, which very quickly grew to a point where you just had no idea what's going on. You click the button and a bunch of events fired and you really couldn't tell what was going to happen when you did anything on the page or there were still a lot of query stuff, things like that, where every state update caused a bunch of rebrand or and a bunch of Tomaree flows. And it was just terrible. So then, oh, and the other popular one was angular with bi directional data flows where you had bi directional binding and you could change the state of some variable and it would magically update everything dependent on that variable, which sounds really cool when you're building small temples. But then it very quickly becomes very painful and grows, gets out of hand. And you just actually it basically becomes pretty problematic very quickly. I don't know if you've worked with code bases like that, but it doesn't feel nice when you change something and you don't know exactly what's going to happen, or you have to trace too much of the code to figure out how things are actually working. So the indirect uni directional data flow was this wonderful idea from the Facebook team from Facebook reacting where you could suddenly understand how your entire application work data would always go just in one direction from the top, from your top component, all the way down to your component tree to the bottom where something changed. So you always knew exactly what was happening. The idea was that your your UI would be a direct representation of state. You would always know, OK, this is if I change state. I know that everything updates and I don't have weird additional things on top of my hate updates, actually, D. Sorry, the natural notification is really bad. So I have to prove that maybe I can see the chat while I'm doing. It just shows. Very good. OK. So now I can see the chart. I don't know if you can see the chart while I'm sharing the screen, but whatever. So basically the idea of the unit directional data flow was that it makes your application more understandable. Your data is going only in one direction. You always know what's happening. You is a pure representation of state because you have all of your state stored in a single source of truth. It's very easy to sync between different components so you can know what's going on. You can say, OK, I'm rendering something all the way here on the left. And it depends on something, a component over here on the right. And you connect them together over the single source of truth and everything is updating in super awesome. So that was nice. But I think we kind of, you know, started taking it way too far. Are we everybody who was teaching react back then? And I think it still happens a lot. We're saying you should put everything in a in your top component. Top component should hold your state and it should hold the state for your entire application. And then you've had to deal with prob drilling. You had to pass everything down. Then you had to pass callbacks back up this up the state's tree. So the problem prospering was a huge problem. You ended up with extremely tight coupling between components. So if you wanted to move a component from the bottom of your tree or of your victory to a different part, you suddenly had to change every component that was on that path. So everything that was between the component you were actually changing and the components that was so everything that was between the component that health state and the component, you were actually changing. You had to change all of that if you wanted to move your components somewhere else. So that was a huge pain in the ass. So people, very smart people were like, you know what we can save? We can solve it with state libraries. So then we had this proliferation of state libraries. We had redux. We had more backs. I think we had a couple of others read books and movies and read books and more books I think are still the main ones that people use. And they're great. They're awesome libraries. Amazing to use when when you're starting out. Well, maybe they're not amazing to use when you're first starting out because it makes your application really complicated. It makes it very hard to know. It makes it relatively easy to know what's going on. But the complexity of your application basically just explodes. Suddenly you need to change five files. If you want to change how something works, you need to add reducers, you need to add actions. Then people are saying you should have all of your actions in one file, but then your files become too big and you start changing things into sagas or Tunks or whatever or other things that are basically your code base becomes really difficult to understand. Whenever you change something, you suddenly don't know everything it's connected to, which is fine in a way because potentially if the abstractions are well done, you don't want to know everything that's connected to your component, right. Or connected to your state. You also get this really nice separation where you have a state layer that handle that lives outside of your component tree so that so it becomes easy to to move components around. You just hook them up to your redux state or to your Mobic state. And it kind of just works, right. It's a pain in the ass to set up. You have to deal with a lot of boilerplate code to get everything working and set up and running. When a new junior joins your team, they probably really struggle with how to make anything work here. There's just a lot to understand. You have a lot of complexity. And what I think is the biggest problem with these libraries is that they break the collocation principle. So suddenly what you're doing and what you're displaying or your UI and your business logic or user interactions and all of that are just not close together anymore. You have to keep a lot of context in mind to be able to make any change. You have to I don't know how often you approached a redux or Molbeck code. Best that somebody else built. You weren't the one building it. But if you don't understand the entire system, it becomes almost impossible to understand how anything works. I remember walking into a saga based, Osaka based project once and it took me three or four days just to barely understand how it works, because you could click something and a bunch of things happened and then a bunch of other things changed. And I had a bunch. I had a lot of unknown side effects where things were rendering and changing that I didn't even know existed. Or I wanted to just change how a button works and a change that I made. I made that small change in the button and suddenly. Heather started changing and I was like, what? No, I didn't. I don't want my head to change at all in all cases. Maybe I just want to change it in some cases. So all of that becomes really difficult and means that you have to be able to basically hold your your entire code base in your mind, which is great as long as it's a small code base. If you use and this principle of splitting up your monolith, it's great because you can have small, simple apps, each deployed in a separate gym stack or whatever you deploy them. So that kind of helps with that complexity. But eventually your complexity is going to grow. I know we tried to work with Micro and that never really went anywhere and was a huge failure. So what I'm gonna propose today is a new way of managing your state that mostly solves all of this problems and makes your life a lot better. I'm calling it the world one called state management because it's based on the idea that you have a lot of local state localized to the smallest possible part of your state. Three, and then you have wormholes to make different parts of your state to communicate with each other. So the principle is that when you have states that belongs just to a single component, where just single component cares about that, you can just use local sticks with react hooks. Now it's super easy to use local state without too much trouble. You don't have to deal with the set states. You still have a sad state function. But if you don't have to build a class based component just to have state, you can use functional components which are now, in my opinion, nicer to work with. And you can use look state locally and it's self-contained within the component. Anyone who looks at that component can always know everything the death component does, all of the business logic it's using. You can then if you have different components that work tightly together that really actually tightly coupled like Will. We'll do some examples later. But let's say you have a button and a display component next to it that shows how many times that button has been clicked. You can just share state between those two components. You don't have to go all the way around to your global state just to change something that that's right next to it. Imagine imagine if every time you wanted to ask your coworker to help you out with something, you would have to write a form, a formal request for help. Then you would have to pass it on to your manager. Then your manager would pass it on to his manager. Then his manager would pass it on to her manager. And then the CEO would look at that for the quest for help. And she would be like, yes, it does sound like with its needs, needs and does help. OK, this is good. And then she would put that into H. R and then H.R. would look at it eventually. And maybe five days later it would come back to Andy, who's sitting right next to you, and she would say, yeah, totally. You can just click that button over there or just copy paste that thing and it's gonna be fine. So that's that's kind of how we work with Redux and mailboxes. You go all the way around to do the simplest possible things. So with the more with the one pattern, you get the collocation of logic. So anyone who looks at the components can always understand that components you minimize you as a result. You also minimize the amount of code that needs to be understood because you'll see with some X Hoke's, you can then make it very easy to share state between components without introducing a lot of extra code. You're not using any libraries, which is great for performance because it means you're not downloading extra libraries, you're not compiling extra libraries, you're just using every stuff that comes with reactor ready. And at least in my experience, the skill, the same approach scales pretty well from very small abs that you're building is just a tiny example. Two huge abs that need that have a lot of different layers. So a lot of different segments of the app that need to work together. So rather than talk to you and talk your ear off about the theory, I figured I would just show you how this works. If we go to this code sandbox over here, you should still see my screen. And I want to I'm going to show you how the wormhole state management pattern evolves from the smallest example to something that is more like a bigger app using code and a bit of life coding. I haven't prepared a whole lot in advance because you don't need a whole lot better. That's kind of the point of this pattern, is that it's simple and easy to understand. You will, however, need to the relatively familiar with hooks, because if you're not, then it doesn't work. So let's say we have we have this cotan box over here and we want to make this plus one button. That is a simple counter. I know that this is a contrived example. That's kind of the point is that we don't have to worry too much about the actual logic behind it. I can then show you how I use this in a more serious application. But the basic idea is that if you have a counter component, it doesn't really need a whole lot. You can stay that you have a count account and they said can't function. You take those from states, you can use a starting propter, said the starting value. Then you create a plus one function which calls cell count, gets the current count and returns count plus one. So very simple function. Pretty simple logic. We when we click the button within the call plus one. And let's also display the current count like this. So I'm going to add a breakpoint here with that. So now when I click on this, you see the count goes up. I have I'm using completely local, state. All of my logic is contained in the component. And it's still a functional component. I'm just saying, okay, count is now a state. Whenever I call the set count function, it. It takes as its first argument a it takes it takes a function as an argument so that we can make it more dynamic. Rik Kirkland comes from state and updates the state, which then triggers only render of the contract component. So we now have a counter component that works completely independently. We can make multiple of them and it's still going to work. Might need to add me. Let's turn this into a device so that they don't overlap. So we now have two counter components that have separate pieces of steak. And you can render this anywhere. And it's completely independent and it always know what's happened, knows what's happening. Let's say this one has to start from five. So when this reloads LC coats and boxes it nice enough to actually maintain state between reloads. So that would nice as well. So you see that hot loading works with this approach and all of that stuff. Now, let's say that the each count component needs to have a separate display component. And that display component could be further down in the three. But they're two. They're basically two components that work together. So we're gonna say it's it's a display and it's gonna count as a normal prop with them. It's a constant display is a component of guests. Some count. And it returns a function that's going to render paragraph, which is going to show that. Now no longer need the break point. Now, I know this still looks still looks kind of lame and like a stupid example, but imagine if this were three components deep or if it was like this could be, I don't know, a a widget on your page somewhere that has something where five the five components need to work closely together. We can also say that, okay, you know what? These default buttons are kind of lame. Let's make a red button component that looks better. So we create a red button component. And this one is going to get the set count called back and we return a button that has all that we said plus one. So let's put in plus one. And it's click and let's give it a back rub red so that you can see it. It's the red button. So we now have a shot to actually pass it in. And the nice when when it suddenly starts to reminding us that we forgot stuff. So we have we still have a pretty simple approach where we have a button that does counting. And we have a display. And we are doing this is basically the old prop drilling approach where we have localized state that is shared between two components that work very closely together. This is great for when you need to build widgets that are relatively self-contained or widgets that are having trouble coming up with a fun example. But basically, imagine if you had a header that needs to share some state between just a couple of components in the header itself. You could do that with this approach. Now, what if we want to contain connect both counters together so that they are counting with the same the same global state? So now we have components that live far apart in the state three. And they need to work together to manage global state. We could go with Redux and do a bunch of stuff. But instead, what I prepared was a wonderful context provider. So we're now going to create a wormhole that's going to share state between two components that are very far apart. And these two components will have in an indirect way of communicating without us. What we're gonna kind of we're kind of harvesting state, but not too much to do here. So we're going to import the one content provider from coal. There's an order complete from dot slash one pork provider. And the wormhole context provider is a react component that does a few things with its own local state to make the state sharable. And I'll show you how that works. So we take these two and we render them inside the room for context provider. It's that so they're now both children of the same global provider. This is similar to your redux provider or your mailbox provider, except we're going to do a few tricks to make them easier to use. Let's see. So we have the wormhole content provider. Now we can go into our counter and instead of using state like this, we're going to say we're going to take state from our context and we're going to take a little function. I called sweatshirt count. So cetera count. And we say use context. It's going to be local context, which we also need to support. So import wormhole context from here. And I'll explain in a little bit how this works. So now we have display count, which is going to display state dot share count. This is where if you're using what's it called if you're using typescript, this makes it easier because it gives you autocomplete on all of these states and stuff. I didn't want to deal with that because I just felt like it was too much for this example. And now, instead of said Count, you're going to set share count and we're going to just set it to states, share count state shared common. Oh, did I actually call it plus one? No, it's not OK. So we take the new state torture count plus one, and no, in theory, this should just magically work as long as we import use context from react. Let's see. So you see now I'm clicking one button and they're both updating. Yay! Super awesome. Right. And we'll go one step further and make this even easier to use. So we now have two buttons that are connected via a context provider. So the card, the react context kind of works as a wormhole in the background to connect two components that are into your very far apart. How the contact is, does that is that we are creating a context called wormhole context. This would in practice, you would probably have named this differently for different sections of your application. I forget what those would be called in redux because it's been around two years since I seriously used Redux last. But the idea is that you create this context. You put in some states and you put in your actions or your functions that you use to manipulate. That's it. We're gonna make that even better in a little bit. So then when you create a wormhole context provider, a functional component which has a state using just a local state, which is a state or with a single variable. But the variable is an object. So we are able to have both a shared account. And you can add, let's say random strangers, says random. And I'll show you how that works later are actually not showing up. So if we now have random string and we want to display that over here, we can say state of the art, random string. And it should show up right next to us. Let's see. Do I need to reload? Yeah. So we now have random string. So basically, we're using a single piece of state that that state can contain an object which then has a richer, more complex piece of state that you that you actually use for your application. So we have that wenko context. We then we have our initial state. We have our state state. And we have the context value, which is another piece of you state from react that holds our application state and help or any help or methods that we need. So right now we just have section share count. You could then add more actions here or I'll show you in a bit. You can use reducer to clean up this part as well. Then to avoid having multiple re renders whenever. So to avoid having multiple reminders, we then use a use effect to ensure that our component only reave renders when we when our internal state changes. So not when the context value changes, but when the state stored inside the context value in object changes. Then we render a wormhole context provider with our context value and all of its children, which gives us the nested the nested stuff block. So what's happening now is that when. When we call such a count, that court that goes into react on text by a deed, by the provider goes into context value. Now, let me change from explaining that. So when we now render something, we take the warm context out of our react context and we take it state and a setter or a helper method when we call that helper method. It's actually being passed in from our context provider, which then calls this function that we defined here. That function calls set state and updates the state, which then triggers a re render via this effect of our entire application of everything that depends on that state. Now, this isn't this potentially isn't as performant as something like mailbags, because it doesn't keep track of what's actually listening to which parts of state. But in practice, it usually works well enough if you're using redux. This I think this is actually I haven't benchmarked it, but this should be about as performance, as redux. So we have that. Now, what we can do to make this even easier to use here, do you see we had all of this stuff, right? So we have a context and we have something that lets us manipulate that context. And it's kind of nice. It's nice that we have it in the content component, but we can actually make it even better. Let's change. Let's turn this into a custom component. So we're going to say constant use, shared counter use, shared count, which is going to be a let's just call it a function, function, use, share, count, which doesn't really take anything. And it takes it takes stuff out of context, creates a plus one function, and it's going to return our current state Gotcher count. And the plus one functions usually easier to do this way should count. And the plus one function. So we now have a custom cook. Why is this complaining? OK, it's fine. So we have a custom cook cook called you shared count which talks to our react context creates the wormhole, creates a f an easier to use function that just does plus one and export and returns both of those as the count and plus one. So now when we go into the counter here, we can say cost plus one equals use, share, count. That will just work. And then display can be turned that into a display count components. So you can display count which no longer needs to take and drops. Instead, what it can do is say should count, cost, shift, count equals use, share, count. So this should now just work magically. So let's see if it does. Yep. So we now created a hook that we can use in any component, no matter where it is in our componentry that independently connects to our global state or whatever shared state we have and uses that state to what, just locally in the same component. So now if you're if you imagine how this works from a. What did they do? So now if you imagine how this works from the perspective of a junior that's joining your team or even you who hasn't been working on a part of the code base for six months or whatever, or you've just forgotten what you're doing or how it works. You can come to the counter component and you say, OK. Using the shared count, which is probably some context, and we're just taking out the plus one plus one function and we call it in the red button on click. Cool. We can even completely remove this and call this the plus button. So we change this to the plus button. We then move this over here. So we're using the shirt. Can't hook directly. We call plus one and you see it's still going to work. So what what you just saw is how easy it is to refactor your components. It's easy to move using shared state to a different two completely different components. All of these components or components are relatively independent. They're no longer tightly coupled. They can always get their own state from the ship, from the warm coat. And they can always use that state to do whatever they need with it, which is really nice because you can then it's it makes it easier to move things around, makes it easier to refactor. It makes it easier to say, OK. This is the plus the plus button, just X plus one from your ship that comes and does stuff with it. And we didn't have to use any leverage to achieve this. We just used a bunch of default. We like OK, we used a bunch of default react machinery and it all kind of just works too. You can make this even simpler if you go to go here and instead of your state, you can then use reducer. Not going to show you how to do that because I think it would take too long. So they used to serve for more flexibility. And then here, instead of a reducer, you would then just pass in the despatch function on your Lydersen and you'd then get really caught. And then you get basically the same complexity. Well, not the same complexity, but you get the same power that you have with Redux where you can call this patch on your reducer, which then triggers the same stuff here, the same stuff for your. For the Randers and all of that kind of kind of trailed off. I'm sorry, but the idea is that if you like reducers or if you have complex state that needs to work together, you can use reducer and you still get all of the same ease of use. You get the same flexibility. You get the same collocation of your logic and your what. You're actually using that logic and you can still still have all of the power that you need. One other thing that I wanted to mention was that with this pattern, I know that in Redux and mailbags and a couple of those others, it becomes very complicated what to do when you have asynchronous actions with with this approach. It doesn't have to be complicated because this can be an async function. So you now have async function that just works and calls set state whenever it's ready. And we react with the update and we render your stuff and it's going to be fine. You don't need to you don't need any special knowledge of how things work. It still just functions. It just functions working together, its functions. And you can insert asynchronous stuff wherever you want. So that was that was what I had to show you today. I know it's kind of a pretty simple example, and I contrived acounter as much as I could to make it. To make it understandable where I'm going at the wormhole state management pattern, but still understand also we're not getting into the weeds of application logic. So, yeah, I hope you enjoyed the talk. And I hope that this gives you useful ideas for how to approach stuff. I know I'm pretty sure you can use this with redux or in any existing reactor application. You can just start using it and it's gonna be fine. I know it's not a library. It's just a pattern which I prefer because it reduces dependencies and helps you. It's more about how to think about state management versus use this library and it's going to do everything for you. Which I prefer. Yeah. So if there's any questions, I'm around. Otherwise, you can hit me up on us on Twitter later. And that's it.