Dominik Dorfmiester === Dominik: [00:00:00] Hi, Noel: front end engineer, and he's here to talk about component composition. Welcome to the show, Dominic. How's it going? Dominik: I'm great. Thanks for having me. Noel: Perfect. Perfect. ~Uh, ~I'm excited. ~Let's jump right into this one. Um, ~can you tell us a little bit about ~what, ~what you mean when you say composition? I feel like this is one of those things where it's like, people don't think a lot about like how to break their components apart and put them back together and like what logic belongs in a component and how to split those out when we're talking composition, what are you talking about specifically? Dominik: Yeah, so I would say that, ~you know, ~components,~ um,~ basically can take other components,~ um,~ either as children or in other slots,~ um, um, ~props that they're taking. So when you compose components, it basically only means that you pass one component,~ uh,~ to another component. So for example, when you have,~ um,~ [00:01:00] a select component that takes in,~ uh,~ options,~ um,~ You might have a select component and it takes in the options as a children,~ uh,~ components rather than,~ uh,~ having an array of say,~ uh,~ label and value,~ uh,~ objects that get passed in and then get rendered as options by that component. ~So, ~Component composition is just,~ um,~ having multiple one or components being passed to other components. But I think,~ um,~ this is not ~where, where, ~where it stops or where we should actually think about, because I think we do that on a daily basis when we, even when we have ~like ~a diff component, ~right. ~From react and we pass children to it. So this is ~kind of ~like normal markup,~ how,~ how you build it. ~Um, ~component composition can be a lot more in react. And I think this is. ~Uh, ~yeah, where maybe some experience is needed or where,~ um,~ some developers might not have seen that work in a way before, which is why they haven't ~like ~thought about it or tried it out before. Noel: ~Yeah. Do you, do you think, um, but there's like kind of, Um, I guess~ I'm curious why you think developers struggle with this or why this is like a problem,~ uh,~ that isn't something devs, ~you know, ~like naturally end up doing. Is there [00:02:00] something with ~like ~the way that we try to separate concerns in React or the way that kind of React's APIs themselves work that make this ~not, ~not super intuitive all the time? Dominik: No, I think mainly,~ uh,~ the way I've seen it is usually how components evolve over time. I think if you use component composition, And,~ uh,~ also early returns in components. I think those are components that can evolve very well over time. And you might have components that don't need that, right? If it's something that you've written once and then you never touch again. But my experience with working on,~ uh,~ on teams is that you write a component and you maybe render some static children that you pass to another component. ~Right. ~And then requirements change. ~Um, ~and you might add a condition to it. And then when you make that first conditional rendering. You basically start,~ uh,~ introducing a logical branch for that. And then you add some, you add something more. And,~ um,~ I've seen this a lot,~ uh,~ also as a,~ uh,~ as a maintainer of react query, where you have a query that's made in a component, and then logically you already have three, maybe [00:03:00] four different states that component can be in, depending on the data that it gets back. It's usually could be in an, in a pending state. It could be in an error state. It could have data, or maybe there's also this like empty state where a component has fetched data, but ~it, it, ~it doesn't really get anything meaningful back. So you want to render like an empty screen ~with, ~with more instructions. And then all of these things usually evolve over time and you add more renderings and conditions into your component because you have some markup at the top, maybe some layouting. ~Um, ~and then those children start to become not just components, but react expressions or JavaScript expressions that. Conditionally produce React components. And that's where it gets ~really, ~really complicated to read. ~Um, ~I think because it's no longer a logical flow from top to bottom. It's not just, ~you know, ~that div that takes a button or it's not just a select that takes that options. But it becomes this, I don't want to say business logic because this is such a,~ uh,~ I don't know, weird term, ~I guess, ~but it's just logic ~that, ~that you need to render your [00:04:00] component and that evolves over time. And then you get ~like, ~you have this discussion about, do you want ternaries or do you want like the,~ uh,~ the rendering where you just join an expression together or whatever. But,~ um,~ yeah, it's at the end of the day, it all becomes quite complicated with expressions inside JSX. And this is. Where I think it's time to take a step back and see, are there any other ways that I can break up my component? ~Right. ~Is it really just going to be that div and then ~like ~one expression and a bunch of,~ uh,~ ternaries in there, or is it going to be a different split? Noel: ~Yeah, ~yeah, ~I've definitely, ~I've definitely seen components that are in, ~you know, ~like nested ternary hell or just like ~a ternary inside ~a ternary inside a ternary. And it's just ~like, ~this is no one would ever ~like ~accept this in normal JavaScript and it's wild that we let it get this far. I think because we're in JSX and it's just ~like, ~we ~kind of ~see this as. It's easy to get the blinders on and just be like, this is the only option. This is how I do conditional rendering, because I have to ~kind of ~do it in this pseudo inline style, right? Cause I'm like trying to do it inside of a JSX,~ uh,~ like template. ~Um, ~do you think that there is something. [00:05:00] Like something about this, like reacts abstraction here that makes ~this, ~this kind of evolution ~of, ~of components that get really gross like this and have the, all these like ~kind of ~interwoven branching conditionals ~is there, ~is there something about this format that leads devs to doing this and ~not, ~not checking themselves? Cause I have a hunch. Devs writing like normal JS code would again, ~like ~not think that this is okay. We just ~kind of, ~when we're ~in, ~in react template land, it's ~like, ~yep, this is just how it is. Why do you think, why do you think ~that ~that is? Dominik: Yeah, I think in part it's because,~ uh,~ in JSX, you can only write expressions. So things that actually produce a value. You cannot write statements like an if statement or,~ um,~ something like that. ~And, ~and this is quite limiting in, I think it's,~ um,~ it's just how it is. ~Um, ~but when you think that this is the only option that you have, because you are already nested inside,~ uh, like, uh, ~the children of some component. Then you just start to do it. And ~you know, ~inside JSX, I think when you do this conditional rendering, and then maybe you put in a fragment in the, like in the else part of [00:06:00] your,~ uh,~ ternary, and then you put another expression in there and then you have another ternary in there. ~You know, ~we have linters that forbid nested ternaries, right? In normal JavaScript code. But once you get into this. ~Um, ~condition that renders a fragment and then another condition, not a fragment. It's ~kind of ~like this isn't checked. So nobody screams at people. ~Uh, ~and then there's ~like, uh, ~when you look at it in the code review, you might be thinking, yeah, I don't really know what it does, ~but you know, ~whatever. I just look at the screen and then I see what the output is. ~Um, ~yeah, this is usually ~how we, ~how we wind up in these situations. And there are other frameworks that solve this differently. ~Um, ~for example, I think in Vue, you have the option to ~like ~render these when and if statements or ~in, ~in,~ um, in, ~in SolidJS, you also have those. And you basically try to put your logic into kind of like ~the, ~the markup itself, like markup components, or I don't know how to call it, like an if component, for example,~ um,~ I don't know if that's better. ~Uh, ~I'm personally not a huge fan of that. I haven't worked with this professionally, but,~ um,~ just from,~ uh,~ the way I'm imagining things, of course, this is ~kind of like, uh, an, an, ~an opinion of someone who hasn't really used it. ~Um, ~I don't think it's the solution, [00:07:00] but yeah. ~Um, ~React, just from the fact that you can only use expressions ~kind of ~struggles with this. Noel: ~Yeah. So, yeah, you mentioned, ~you mentioned early returns as ~kind of ~a general,~ um,~ pattern to help with this. ~And I, ~and ~I think that, ~I think that you're right, I guess to help it, this is what we're ~kind of ~far enough in the weeds that ~it's, ~it's hard to explain this stuff, ~you know, ~just verbally sometimes, but ~can we, ~can we do, can you give us a quick overview of how an early return can fix this problem? Maybe with your like kind of three state component that you were explaining earlier, where it's ~like, ~has data is loading or is empty. Dominik: So ~when you, ~when you start out in a component,~ right,~ you are basically a component is a function. So you are in like, let's say pure JavaScript land. ~Um, ~you can write if statements, you can write,~ uh, uh, ~while loops or whatever, if you want, ~right. ~You can,~ you, you, ~you can write any,~ uh,~ statement in there that you want. ~Um, ~But as soon as you create a,~ uh,~ a React component, you get into these situations where you can only write expressions again. Of course, this is, there is, there's a JavaScript proposal, actually about do expressions that will then evaluate to a value. ~Um, ~but you can write ~like, uh, ~in a block, not normal JavaScript,~ uh, uh, ~conditions in there as well. ~Um, ~I think that this [00:08:00] will be helpful. It might also just be like more confusing because you add like more blocks and things into your JSX. ~Um, ~but it's one way ~to maybe, ~to maybe fix this. ~Um, ~but as long as you are on the top level of a component, you are in, let's say, just JavaScript land. So there you can just write if statements and ~you, ~you write your query, you write your hooks there because, ~you know, ~hooks can't be called conditionally anyways. So after that, maybe ~you, ~you, you call a query and you have like your three or four states. So you can start off with an if statement, and you might want to check for the most important condition first, right? So you're checking if I have data back,~ um,~ I'm just going to render my data. ~Um, ~and if I'm in error state, I handle it differently. And if I'm in loading state, I handle it,~ uh,~ in another way. And you just write these three if statements, and then you're done. And the beauty about this is that when, once a fourth state emerges, ~you know, ~you can just add another if statement in that line and,~ uh,~ wherever it's appropriate and do another return and you're done with it ~while, ~while you are inside JSX, what happens is that since you need to [00:09:00] do,~ um,~ those conditional renderings. You have to check for,~ well, um, ~basically a ternary that says, is it loading? Then I render the loading spinner. Otherwise I render null, right? Inside your,~ uh,~ like diff or layout component or whatever. But the moment you do that, ~you aren't, ~you aren't terminating the component. You aren't returning from that component. So whatever comes afterwards has to check if I'm not in loading state, right? So you have to ~kind of ~say not loading. ~Hmm. ~And arrow or whatever, like you ~kind of like ~try to,~ uh,~ move through ~all those, ~all those different branches. And when you add a fourth condition, like you have to make sure that you're not inadvertently rendering something else. Noel: ~Right,~ Dominik: ~Um, ~and that, Noel: three of the other conditions when you have a new state. Yeah. ~Mm hmm.~ Dominik: And the,~ and,~ and the other thing that goes really well in here is actually TypeScript, because when you do early returns with TypeScript, the TypeScript compiler can help you,~ uh,~ eliminate those,~ um, you know, ~types that are ~Um, from the, ~from the equation. So if you're saying, if I don't have data, I do something afterwards, data will always be defined. You don't need optional training. You don't need further null checks or whatever. If you just do this in an expression and conditional rendering inside [00:10:00] JSX, this doesn't return. So it doesn't narrow. It doesn't, it probably narrows like in that line of code where you have like your ternary, but it doesn't narrow for your code that comes afterwards. So this kind of ~like ~makes you add lots more optional trainings or whatever,~ um,~ to your code and that's ~kind of like, ~yeah, makes it also more complicated. Noel: Yeah, I feel like that is a, it is a clean, it's ~a, ~a pretty clean pattern. I think ~it, ~it, it reminds me of ~like, ~I feel ~like, you know, ~you'll see,~ uh,~ just talking about normal JavaScript, you'll see like conditional assignment happening, like in if statements, and I'm always like, just break this out and make this like an early return that always gives us a static value. So we can just call a function, get a value back. And we don't have ~like ~this cognitive load of state. That we have to ~like ~manage throughout, but it does feel a little bit different because I think it goes back to what we were talking about before, where it's like the particulars of JSX or what make us write code this way, because again, like we wouldn't, I feel like intuitively we would never be like, I don't want to have to check all four of these paths every time I make any change to this code because each one of them is just like this, ~you know, ~funky ternary. ~Um, ~so yeah, ~I think, ~I think it's good.[00:11:00] Dominik: Yeah, no. ~Um, like, um, ~I think cognitive load ~is, ~is really ~what, ~what matters ~when you, ~when you try to read a component and you don't immediately know what it's going to render, you're basically seeing a bunch of loading,~ uh,~ turneries and then,~ um,~ error handling, you And then ~it, it, ~it starts to creep up into multiple,~ uh,~ locations. ~Um, ~we have an,~ we,~ we had an example where like we have ~this div, ~this div or whatever ~that, ~that wraps around this. And it basically needed to show, I don't know, maybe it was a layout component that needed to show like an additional icon in some cases. So all of a sudden you would have to ~like ~duplicate those conditions and pass them around to multiple components. So it's not just that this stays inside the one component. And ~we are, ~we are, we're using a, we're trying to use a lint rule. It's not perfect. ~Uh, ~it's called, I think it's called complexity that basically tries to analyze like how many branches and conditions do you have ~in your, ~in your component, in your function. And then it basically warns you if that becomes too complicated. And every time when this happens in a react component, it's ~like, ~it's by no means perfect. There are some situations where it's just unavoidable, but most of the time when [00:12:00] this rule,~ um, you know, ~alerts us to a react component. It's because we have so many conditional renderings inside. And every time I break this up into,~ um, into, ~into like early returns, the complexity goes away. And that's,~ uh,~ that's astonishing, I think. Noel: Yep. Yeah. Do you think that this, is there any,~ um,~ I guess I feel that there, there is like some counter argument here, wherein by breaking things down into like subcomponents ~with, ~with early returns, like there are certain kinds of changes that may need to happen in the future. That may be a little bit trickier to have to ~like ~go in and change some of those children to get them to bubble up correctly. I guess. Have you ever encountered that in the wild? ~Um, ~Or is there like a, ~you know, ~an easy counter argument you'd make that~ you don't, ~you don't think ~that ~that's, that happens very often? Dominik: I think it doesn't get like the way I see it, it doesn't get used enough. So I don't have ~like ~the sample size ~of, ~of all of the things,~ uh,~ the places where,~ uh,~ where I've reflected codes towards this pattern,~ uh,~ has always been like,~ uh,~ magnitudes better. ~So, uh, ~can't say that I've seen. Uh,~ any, ~any of those drawbacks, it's maybe ~like, ~if it's a [00:13:00] really huge component, the most,~ uh,~ like critical,~ uh,~ part is how do you break it up? Like, ~where do you, ~where do you draw the lines and.~ Um, ~I think what helps here is also what's in the react docs that actually suggest that the first thing that you do is you look at it from a user perspective. So you ~kind of ~like you draw a mock up and you look at the various States that your component can be in. And then you're just trying to see, okay, what is the user actually seeing in any of these States? Because I've seen like one of these components with conditional rendering that,~ um,~ I think ~it, it, ~it checked for data or for not data or whatever in ~like ~four different places. And I was just looking at this component and I was asking myself, okay, if this component is like in a loading state, like what is the ~user ~user actually seeing which, ~you know, ~which text is displayed, where is the load displayed? And ~I couldn't, ~I couldn't figure it out because there's so many conditions that you need to basically compile in your head,~ um, to, ~to get to ~like ~an output that, that you're seeing. And that is this cognitive overload that is. ~Uh, ~that is so important to,~ uh, uh, ~yeah, to reduce. So when you then go through the drawing [00:14:00] board and just ~like ~take a whiteboard, whatever, and just draw the screen that the user wants to see, you will naturally come up with groupings. that,~ uh,~ will very likely be a much better grouping than what you currently have. Noel: Yeah. ~Is there, ~is there any ~like, um, ~concern with kind of breaking, I guess fragmenting too much? Like ~breaking, breaking, ~breaking out too many sub components, all like everywhere. ~Have, ~have you ever seen the opposite, ~I guess, ~where it's like. We're so close here. This one could just be a quick tertiary, like in this case, like we don't need all this broken out. And if so, how do you ~kind of, ~how do you delineate? Dominik: Yeah. So I think,~ uh, uh, ~conditional rendering or ternary still have its case,~ uh,~ when you actually have something that isn't ~kind of ~like a state of a component, when you have mutually exclusive states, where one state eliminates the other. some other part of your component to render, then this is where all the returns are good. If you just have something, I think in my blog post, I mentioned the example, like a rendering a user component. I don't know if I actually used that at the end, but ~if you're, ~if you're actually running like a user component. And you have,~ uh,~ like the user might have,~ uh,~ a profile picture or not. [00:15:00] You can just do a conditional rendering that if it has like a profile picture,~ uh,~ and then the ternary that renders the avatar component,~ uh,~ with the URL ~of that, ~of that picture,~ or,~ or you're in the null in the other case, and that's just a conditional rendering that doesn't eliminate anything as you don't need to check for,~ uh,~ Like this specific condition further down the componentry, because it's really scoped to ~like ~the one thing that you want to render. And ~if you, ~if you break these things down into separate components and this goes through various layers, it might actually become more complicated. And I think this is, this kind of ~like ~takes me back a bit to functional programming, where if you're like taking it to the extreme and you want to be point free and every function only takes one argument and you break everything down. into ~like ~so many functions that all call each other that compose together. It might be very beautiful, like theoretically and mathematically, but when you actually have to find out like which parameter goes where you're actually traversing like a very big tree and you don't really know, like where are things going and where are they coming from and here I can see that it's like, could be [00:16:00] the same if you break it down too much, but right now the things that I'm looking at is either ~like.~ A huge component or a huge component that is broken up into multiple components, but at the wrong layer, not at the layer where we're talking about,~ um, the, ~the,~ uh,~ mutually exclusive states that we would want to break up. Noel: ~Nice. I think, yeah, this kind of transitions nicely into, into the, the point I wanted to end with and kind of wrap up on here. Can you, like, how do you, how does your mental, um, kind of mapping work? Like what, what is a good example? Maybe you have a component. Or, uh, logic that is broken up like into components at the wrong, in like the wrong layer, the abstractions are not good.~ ~Is there, is there a, is there kind of a smell there? Is there anything that's like kind of leads you to believe that maybe we need to do some refactoring beyond just, you know, like early returns in a given case?~ Dominik: ~Um, I need to think of it, but can you maybe, uh, uh, repeat the question~ Noel: ~Yeah, yeah, here.~ Dominik: ~fully got that. Sorry about that.~ Noel: ~I can kind of queue you up. Yeah, I think I might've cut out a little bit. Um, yeah, that, that kind of, ~that kind of transitions nicely ~into, ~into something I wanted to end on here in that,~ um, like, ~I guess I'm curious if there are any particular patterns that are easy to recognize or would be a good signal that there is some kind of,~ um,~ functional problem, like in your component design, like there are bad abstractions being made and maybe you just ~like, ~Early returns like we've been talking about here aren't actually the solution. And there's some, ~you know, ~higher level problem as to how components are broken out. Is there any particular signal that may be that it's time to go back to the drawing board on like a piece of logic ~and, ~and ~kind of ~evaluate what needs to be happening? Dominik: So I, yeah, I usually go back to the drawing board if there's,~ um,~ if there's like this, if this complexity rule screams at me, so if there's too much that's going on, or if I can't, it's pretty hard to say because it comes down with ~like, ~what,~ uh,~ what can I [00:17:00] easily understand from ~like ~reading at it at first glance, and this is different ~for, ~for everybody. And maybe it's just me, but I have a hard time,~ uh,~ understanding Compiling all of those ternaries ~in, ~in, in my head to, to see something. I think component composition reads much more naturally because it reads from top to bottom. ~Um, ~I think this is also maybe where an additional split ~is, ~is necessary. As soon as you basically ~your, ~your JSX becomes so long that you try to extract. One of those Chis, JSX, that you're rendering into a constant that's basically inside the same component because you need to render it further down at that point. I've seen this a couple of times. At that point, the component is actually no longer readable from top to bottom, but you ~kind of like ~define a constant with JSX and then conditionally render it somewhere else. ~Um, ~this is where you've broken it up probably in the wrong place because ~you've, you've, ~you've not eliminated a toy. You've just made the toy smaller. And that's,~ uh,~ yeah. That's. That doesn't reduce the complexity. It just shifts it around within the same component and it doesn't make it any easier, ~I guess.~ Noel: I'm definitely guilty of that one where there's some [00:18:00] really gross turner. And I just, I want the quick fix. I'm just like, I can just break this out into a conditional up above. And it's like the code looks a little better. I would argue like it can be visually cleaner, but I agree that like the cognitive load is exactly the same. This is ~like, ~there's no actual difference here. ~Um, ~other than now you don't have to parse it. Cool. ~I guess. Is there, is there anything else you wanted to, you wanted to touch on Dominic or cover that you talk about in the blog post?~ Dominik: ~Um, well, I mean, quickly look at the blog post. Um,~ Noel: ~I feel like these were my, these are my talking points. I feel like we've been pretty thorough.~ Dominik: ~yeah, no, that's good. I think, I think that's fine for me too. Okay.~ Noel: ~Awesome. Cool. Well, thank you. Uh, ~thank you so much for coming online and chatting to me. I know that I know these like kind of pseudo technical discussions can be a little bit hard to like cleanly articulate just verbally, but I think you've done a really good job. So thanks for coming on. Dominik: Awesome. Anytime. Anytime.