This course has been updated! We now recommend you take the State Management with Redux & MobX course.
Transcript from the "Shared Component State Problems" Lesson
>> Steve Kinney: When last we worked on a project together we started to feel some of the complexity of passing a state down and actions, user interactions back up, right, we had to go through children and grandchildren. And we know that state is stored in a component and passed down as props, right?
[00:00:25] We can't mutate it, we have to keep moving it along, and then passing those messages back up to the place that it belongs. We said earlier in this workshop always to think about the actual state as private. We can show it to the other children components, but if we need to change it, everything needs to go back up to where that state came from, right?
[00:00:43] And that is again a refrain of React. So I actually wanted to round back to a discussion we had earlier, thinking in React. And kind of pull out some more quotes about, we did this a little bit in Jetsetter, we started to figure out where do we need to store the state, and store the actions, and where is the best place for some of these things.
[00:01:06] In some cases it was the application. In other places we were able to get away with it in the items list, right? We found that kind of naturally as we were working on it. But we really didn't talk about what was the guiding principle that lead us to that idea.
[00:01:22] So let's actually look at a few more quotes from this tutorial. So one of them is, identify every component that renders something based on it's state, right? We did that. There were other things that are like sections and divs, and stuff that we didn't do anything with. We didn't make a component for the submit button itself, right?
[00:01:43] We kind of broke things up by thinking about the different pieces of state and interactions and breaking stuff up in a component like in those cases. But where we began to explore during the exercise was finding a common owner component, right? Now, by default, that could always be the application, right?
[00:02:03] The application, if you choose to call it application. But that top level, that component that you use React DOM render and put it onto the page, will by default be everyone's common ancestor eventually, because React puts one node onto the page. But we started to feel like we didn't have to pass everything up to the application.
[00:02:22] We were, at certain points, able to keep stuff at a slightly lower level. And so we're gonna talk a little bit more about that. I just want you to think about that for a second, and then we're gonna round back and give ourselves words for kind of that process that we went through accidentally in the exercise.
[00:02:39] So that is either the common owner or another component up higher that should own the state, right? So we saw that. But this is the one I think is really interesting. If you can't find a component where it makes sense to own the estate, create a new component simply for holding a estate and add it somewhere in the hierarchy above the common owner component, right?
[00:02:57] So this is the first time we're hearing this idea of components whose sole job it is to hold state, right? That aren't necessarily in charge of rendering anything. And so we're gonna play with this idea in a few different ways. There are like, it does go on for a few more things, like which is, though this sounds complex, it's really just a few lines of code.
[00:03:18] That we all experienced, right? Everyone feels like, yeah, it was just a few, it was just a few lines of code, but it was getting a little bit tricky. But it is fair that it is really explicit about how the data is flowing through the app, cuz we literally did it each step of the way, right?
[00:03:33] There's not a lot of magic in React by default, other than JSX and stuff like that. But the actual mechanics of how we do it, we are manually passing everything through, so I think that is kind of fair. But here's a slightly more, I think one that we could definitely argue a little bit more over.
[00:03:50] And then, while it may be a little more typing than you're used to, remember that code is read far more often than it's written, and it's extremely modular, explicit code. And as you start to build large libraries of components, you'll appreciate this explicitness and modularity, with code reuse, your lines of code will start to shrink.
[00:04:07] All right, let's pause for a second, right? Cuz this is gonna be a common thing, right? If you were to go into Jetsetter, or imagine a larger version of it, and you had to trace everything, you could probably do it, right? It was tedious to write, this will always be a trade-off that we have to think about.
[00:04:25] Which is some of the patterns that we're gonna look at for solving the problem of this is tedious to pass everything down and back up, right? We'll make a trade-off, which is things will get a little less explicit, right, as soon as we start coming up with more clever ways to deal with those things.
>> Speaker 2: I remember doing that in Backbone, and it was impossible find out what was affecting.
>> Steve Kinney: Mm-hm, yeah cuz even with that you can mutate stuff too, right? So you can trace it in a React app, so you need to make the decision, we're gonna look at some patterns.
[00:04:56] And you'll be like, I'm gonna use all of this patterns all the time. That's gonna come with trade-offs as well. And we'll get a chance to see, when we look at different patterns, we'll see different trade-offs in each of those. So, yeah. It's unclear whether or not always doing evreything explicitly in the large app make sense, right?
[00:05:16] Cuz it becomes very tedious to do that tracing. And there are times where we might need something across two siblings that doesn't necessarily matter to the parent and, is that really adding clarity to our code? Unfortunately, the answer is that it depends. But it's definitely easier said than done.
[00:05:33] It's very, I think, okay to write it in documentation. But I think the reason that we're gonna see these patterns exist, and that some of these libraries that we're gonna code with exist, is because I don't think just because the documentation says it, doesn't necessarily mean that it's true, right?
>> Steve Kinney: So when you go down this route, as we saw in Jetsetter, it effectively means that all the state needs to live in the topmost common component that needs access, right? And I'm saying that cuz I wanna pause on common component, right? And so I wanna talk a little bit about this article by Sam Selikoff, that was actually written about Ember, right?
[00:06:09] But I think it gives us words for describing what we we're doing between what we put in items, between what we put in the application, between what could happen in the item, which is this idea of the lowest common ancestor. So this is like the, it is, again, in Ember blog post, but it's definitely talking about this idea of like as you break stuff out into components, what are the parent components that need to deal with the children?
[00:06:34] We don’t wanna put everything up in the app, right? That becomes unwieldy if everything is in the application component. So the example of having a little slideable box. You click on the top, it accordions smaller. You click it again, it pulls back out. Is the example that he uses.
[00:06:51] If it was just in the application, you could have a button and a collapsible panel, right, that control this. But then the application has to become the common ancestor. But the application grows, we add a sidebar. All of a sudden, the application isn't necessarily the best place for it, right?
[00:07:06] We have this common ancestor with both the button for opening and closing the panel, and the panel itself lives. That is now the kind of lowest common ancestor, right? We don't have to go all the way up the tree. So part of it, when we think about how do we manage the state in our applications, which is to think about, how far can I push this down?
[00:07:24] So that the application doesn't become the all knowing object that has all of the states of our application, that becomes untenable after a while. But it does solve for problems where almost every component needs to know stuff, right? So current user, right? This is like Twitter, effectively, right?
[00:07:42] Where we've <notifications> in a notification list, and a single notification, tweets in a timeline. Current user's important for all of these things? Right, and in this case, they are sub-components of the app. But these are very likely in different places in your application. And if you have two very disparate places in your application, and they both need the same state, it can be incredibly difficult to keep going up the tree until you find that correct place.
[00:08:08] And that correct place that is common to both of those might not be the place that you would think to look, right? And so this is clearly a pattern or a problem that other people have come across, and there are some patterns for dealing with it. So we have some hard problems that need solving, right?
[00:08:25] We have a small application. But again, we could feel some of the issues with it. Things like deep component trees, where those siblings that both need the same thing, or cousins, effectively, right, are very far down the tree from each other. Even in our small application, this is effectively the flow of it, right?
[00:08:41] We have the application component. We have the new item, right, that was kind of its own, but I had to talk back up to the application to add the items. We have the two item lists, different siblings. And each one of those had the items, singular, they had the filter list, right, that were singular to them, right?
[00:08:59] So there are different places, and this is starting to get a little bit crazy, right? And, if this tree grows, it's gonna be even harder.