# Building Future Facing Frontend Architectures - frontendmastery.com
Synced: [[2023_11_30]] 6:03 AM
Last Highlighted: [[2023_07_13]]
Tags: [[Software]]

## Highlights
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h56f0bpaej60mb36zrvpzp7k)
> **What is the one responsibility of this component?** Good component API design naturally follows the single responsibility principle, which is important for composition patterns. It’s easy to conflate something simple as easy. As requirements come in and change, keeping things simple is often quite hard as we’ll explore later in the guide.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h56eydmpp5gmv7xxy1cbrgjc)
> **What’s the absolute minimum, but complete, representation of its state?** The idea is that it’s better to start with the smallest but complete source of truth for your state, that you can derive variations from. This is flexible, simple, and avoids common data synchronization mistakes such updating one piece of state but not the other.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h56f0aj77h26jf13rdvym5zp)
> **Where should the state live?** State management is a broad topic outside the scope of this guide. But generally, if a state can be made local to a component, then it should be. The more components depend on global state internally the less reusable they become. Asking this question is useful to identify what components should depend on what state.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h56eyq29zh6fqd51wtchsq5q)
> A component should ideally only do one thing. If it ends up growing, it should be decomposed into smaller sub components.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h56eys6kmc7he0js9ecs9a4t)
> A component should ideally only do one thing. If it ends up growing, it should be decomposed into smaller sub components.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h56f10h0arwpnp5h2753zsbd)
> You can build top-down or bottom-up. That is, you can either start with building the components higher up in the hierarchy. In simpler examples, it’s usually easier to go top-down, and on larger projects, it’s easier to go bottom-up and write tests as you build.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h56f1n2wrbax8bpeawnzprf5)
> Top down is generally the most intuitive and straight forward approach. In my experience it’s the most common mental model developers working on feature development tend to have when structuring components.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h56f4jvyq71wnzj6vcszjq6q)
> In practice, a top down mode of thinking tends to fix itself on a particular abstraction out of the gate to solve the immediate problem at hand.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h56f4rk0073fhahpndf4mjtc)
> It’s intuitive. It often feels like the most straight forward approach to building components. It also often leads to APIs that optimize for *initial* ease of consumption.
Making it easy for the consumer is difficult. Don't hide things from the consumer, make it flexible.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h56gk6vmg6kd8qrbs13kancs)
> **A** - Think about whether or not this is the right abstraction. If not, undo it by actively decomposing it before doing the work outlined in their story.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h56gkahy3tdk02j459jmz6tc)
> **B** - Add an additional prop. Add the new functionality behind a simple conditional that checks for that prop. Write a few tests that pass the new props. It works and is tested. And as a bonus it was done fast.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h56gkdac3mwfe7pk2hf3bkrx)
> Existing code exerts a powerful influence. Its very presence argues that it is both correct and necessary. We know that code represents effort expended, and we are very motivated to preserve the value of this effort. And, unfortunately, the sad truth is that the more complicated and incomprehensible the code, i.e. the deeper the investment in creating it, the more we feel pressure to retain it (the sunk cost fallacy)
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h56gkn7cac6bhx5qk7dvqxf4)
> The sunk cost fallacy exists because we are naturally more acute to avoiding loss. When you add time pressure, either from a deadline, or just simply “the story point is a 1”. The odds likely are against you (or your team mates) from choosing **A**.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578f7z643j8q1mdj3apq3c8)
> The problem here is top down components with APIs like this, have to respond to changes in requirements by adding to the API, and forking logic internally based on what is passed in.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578f995mw1m8npcwvx3q4z5)
> From little things big things grow
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h5786wg91dm7221k1ejc7bqy)
> Our initial intention of “just pass down the list and the component will take care of the rest” has back fired at this point, and the component is both slow and risky to make changes to.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h5786c8gqefdmbe0qp7qdvqa)
> Everything should be built top-down, except the first time — Alan Perlis
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h5786hm137afac0jsnaraa1x)
> As we’ve seen monolithic components are components that try to do too much. They take in too much data, or configuration options through props, manage too much state, and output too much UI.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h5787q45q8sgfnxa1nvdegn4)
> What started as a simple component, within a few iterations (even within the same sprint) as you build the new features can be on the way to becoming a monolithic component.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h5787wehnjs1b5mnqcs0zczs)
> **They arise through premature abstraction.** There is one other subtle gotcha that leads to monolithic components. Related to some common models that get instilled early on as software developers. Particularly the adherence to DRY (don’t repeat yourself).
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h57885r1sebdhbr8sjgp8mte)
> The fact that **DRY** is engrained early, and we see a small amount of duplication at the sites where components are being composed. It’s easy to think “that’s getting duplicated a lot, it would be good to abstract that into a single component” and we rush into a premature abstraction.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h5788a1cx9ct36hmkbwcb7tx)
> Everything’s a trade-off, but it’s far easier to recover from no abstraction than the wrong abstraction. And as we’ll discuss further below starting with a bottom up model allows us to arrive at those abstractions organically, allowing us to avoid creating them prematurely.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h5788mxsk03gc84fjhzkt1w4)
> **They prevent code re-use across teams.** You’ll often discover another team has implemented, or is working on, something similar to what your team needs.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h5788pm1qfqjrhs6dj0k5390)
> In most cases it’ll do 90% of what you want, but you want some slight variation. Or you just want to re-use a specific part of it’s functionality without having to take the whole thing on.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h5788zh4mvnzcqtavt7fx2n6)
> If it’s a monolithic “all or nothing” component like our `<SideNavigation />` it will be harder to leverage that existing work. Rather than taking on the risk of refactoring or decomposing someone else’s package. It often becomes easier to just re-implement and fork it into the safety of your own package. Leading to multiple duplicated components all with slight variations and suffering from the same problems.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h5789ncd0rr1s0cv9jqh96zg)
> **They lead to poor runtime performance.** Frameworks like React that have a simple functional model of state -> UI are incredibly productive. But the reconciliation process to see what has changed in the virtual DOM is expensive at scale. Monolithic components make it very difficult to ensure only the minimal amount of things are re-rendering when that state changes.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h5789x1m3967t1qwsyz243ts)
> One of the simplest ways to achieve better rendering performance in a framework like React that as a virtual DOM is to separate the components that change from the ones that do change.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578aj1k5w7mp3wqhdqsjfqv)
> Compared to a top down approach, going bottom up is often less intuitive and can be initially slower. It leads to multiple smaller components whose APIs are reusable. Instead of big kitchen sink style components.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578gckbwnj4hqv157vz339a)
> Bottom-up is initially slower, but in the long term faster, because it’s more adaptable. You can more easily avoid hasty abstractions and instead ride the wave of changes over time until the right abstraction becomes obvious. It’s the best way to prevent the spread of monolithic components.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578gs4vtfgkdttshtaexexe)
> If it’s a shared component used across the codebase like our sidebar nav, building bottom up often requires slightly more effort for the consumer side of things to assemble the pieces. But as we’ve seen this is a trade off worth making in large projects with many shared components.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578hr2yyv82hbyg6km1r0x3)
> The power of a bottom-up approach is that your model starts with the premise “what are the simple primitives I can compose together to achieve what I want” versus starting out with a particular abstraction already in mind.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578m42v03j5vvh2vtc2pp5n)
> **Inversion of control**
> A simple example to understand this principle is the difference between callbacks and promises.
> With callbacks you won’t necessarily know where that function is going, how many times it will be called, or with what.
> Promises invert the control back to the consumer so you can start composing your logic and pretend as if the value was already there.
> // may not know what onLoaded will do with the callback we pass it
> onLoaded((stuff) => {
> doSomething(stuff)
> })
> // control stays with us to start composing logic as if the
> // value was already there
> onLoaded.then((stuff) => {
> doSomething(stuff)
> })
> In the context of React, we can see this achieved through component API design.
> We can expose “slots” through `children`, or render style props that maintain the inversion of control on the consumers side.
> Sometimes there is an aversion to inversion on control in this regard, because there is the feeling consumers will have to do more work. But this is both about giving up the idea you can predict future, and opting to empower consumers with flexibility.
> // A "top down" approach to a simple button API
> <Button isLoading={loading} />
> // with inversion of control
> // provide a slot consumers can utilize how they see fit
> <Button before={loading ? <LoadingSpinner /> : null} />
> The second example is more both more flexible to changing requirements and more performant, because the `<LoadingSpinner />` no longer needs to be a dependency inside the Button package.
> You can see the subtle differences in top down versus bottom up here. In the first example we pass down data and let the component handle it. In the second example we have to do a bit more work but ultimately it’s a more flexible and performant approach.
> It’s also interesting to note that `<Button />` itself could be composed from smaller primitives under the hood. Sometimes a particular abstraction has many different sub behavioral elements underneath that can be made explicit.
> For example we could break it down further into things like `Pressable` that apply to both buttons and things like `Link` components, that can combine to create things like a `LinkButton`. This finer grained breakdown is usually left for the domain of design system libraries, but worth keeping in mind as product focussed engineers.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578mtadh6ybcqzcbz7jpx3m)
> Open for extension
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578mypspm5hy3ppdan5gxrm)
> Even when using composition patterns to build bottom up. You’ll still want to export specialized components with a consumable API, but built up from smaller primitives. For flexibility, you can also expose those smaller building blocks that make up that specialized component from your package as well.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578n04chwy3rp5eh9rzkyrh)
> Ideally your components do one thing. So in the case of a pre-made abstraction, consumers can take that one thing they need and wrap it to extend with their own functionality. Alternatively they can just take a few primitives that make up that existing abstraction and construct what they need.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578nqgva4pvx4gkbdwx0zey)
> **Name components based on what they actually do.** Comes back to the single responsibility principle. Don’t be afraid of long names if they make sense.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578pbf4q9hyj8pvyeh6hgvm)
> **Avoid prop names that contain implementation details.** Especially so with UI style “leaf” components. As much as you can it’s good to avoid adding props like `isSomething` where something is related to internal state or a domain specific thing. And then have that component do something different when that prop is passed in.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578pyx1m5kfbfbh46se0dp9)
> If you need to do this, it’s clearer if the prop name reflects what it actually does in the context of that component consuming it.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578ptp6hdd9kc49a8dgk2c9)
> As an example, if the `isSomething` prop ends up controlling something like padding, the prop name should reflect that instead, rather than have the component be aware of something seemingly unrelated.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578q2faxbe13pepqwzyk0se)
> **Be cautious of configuration via props.** Comes back to inversion of control.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578qgqmxb87psca1ep0pkp1)
> As you’ll often end up wanting to extend the component to have a different, or additional type of child. Which means you’ll add more stuff into those configuration options, or props, and add forking logic.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578r1epke37qphwvyxqt881)
> Rather than have consumers arrange and pass in objects, a more flexible approach is to export the internal child component as well, and have consumers compose and pass components.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578rdbszr2txefp1wz34g18)
> **Avoid defining components in the render method.** Sometimes it might be common to have “helper” components within a component. These end up getting remounted on every render and can lead to some weird bugs.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578rhj89vnstd7e868g8fy7)
> Additionally having multiple internal `renderX`, `renderY` methods tend to be a smell. These are usually a sign a component is becoming monolithic and is a good candidate for
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578rmxnh5dj6eqks5gdkh0v)
> decomposition.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578s80r5t49g87rmg8t2yez)
> Rewrite things and incrementally migrate to the new component
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578s8zqtg2dqm4pfsmt2nmn)
> Break down things down incrementally
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578sp4pryhep41zp65czyq5)
> **The models we have affect the many micro-decisions we make when designing and building frontend components.** Making these explicit is useful because they accumulate pretty rapidly. The accumulation of these decisions ultimately determine what becomes possible - either increasing or reducing the friction to add new features or adopt new architectures that allow us to scale further (not sure about this point or merge it below).
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578ss1b4mxacyzrw1daccyh)
> **Going top down versus bottom up when constructing components can lead to vastly different outcomes at scale**. A top down mental model is usually the most intuitive when building components. The most common model when it comes to decomposing UI, is to draw boxes around areas of functionality which then become your components. This process of functional decomposition is top down and often leads to the creation of specialized components with a particular abstraction straight away. Requirements will change. And within a few iterations it’s very easy for these components to rapidly become monolithic components.
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578sw32nnt2dqs8tnk7nqem)
> **Designing and building top down can lead to monolithic components.** A codebase full of monolithic components results in an end frontend architecture that is slow and not resilient to change. Monolithic components are bad because:
[[2023_07_13]] [View Highlight](https://read.readwise.io/read/01h578tcrrjt5en7734a9m3vvh)
> **We can avoid the creation of monolithic components** by understanding the underlying models and circumstances that often lead to the creation premature abstractions or the continued extension of them.