Decoupling Component And Layout Responsibilities In ReactJS

After my blog post yesterday on decoupling component directives from layout responsibilities in AngularJS, I wanted to take a quick look at the same concept in a ReactJS context. While the follow-up might seem redundant, ReactJS is a fundamentally different beast in the way that it composes and renders markup. Unlike most AngularJS code, ReactJS fully transcludes components, replacing them with new content. As such, thinking about the same problem in a ReactJS context will be rewarding in its own way.

The problem that I've been noodling on is the commingling of responsibilities within the bounds of a single component. As we compose our components to build a single user interface (UI), it's easy to give a component double-duty, having it be responsible for both the layout of its content as well as for its own layout within the context of its parent. Doing this makes it harder to reuse the component and, in my opinion, makes it harder to reason about the code.

To see what I mean, let's take a look a small demo (repurposed from yesterday) in which I have a chat history that contains chat messages. In this version of the demo, the "ChatMessage" component is responsible for both the "message" and the history "line-item," forcing it to know about its use within a greater context.

As you can see, the collection of messages maps directly onto a collection of ChatMessage components. Without seeing the CSS, it's hard to tell, but this requires the ChatMessage component to manage the layout of the history items, including things like "margin-bottom" and ":last-child" selectors (to prevent margin on the last item in the history):

To create a stricter separation for responsibilities, we can have the top level component manage the chat history and let the ChatMessage component deal with nothing but rendering the message content. The difference in the following code is subtle but meaningful:

In this version, the actual markup of the ChatMessage component didn't change at all. The markup difference happens in the calling context, where the collection of messages is mapped onto a collection of "line items", which individually contain a ChatMessage instance. By creating this separation, however, the CSS for the ChatMessage did change, relinquishing the margin controls to the contextual layout.

Again, the difference here is very small. But, I think it makes the code easier to understand and maintain because it creates a cleaner separation of responsibilities.

All of this makes me think about rendering HTML tables. If you look at my "Thinking in React in AngularJS" post, you will see that the entirety of the Table (including its rows and cells) are rendered by a single component. This differs from the original "Thinking in React" article which has each row rendered by a different component.

At first, I thought that this was a defect of AngularJS - a feature that it could not parity in ReactJS (at least not easily - it can be done with janky double-transclusion). But, when you think about the responsibilities of layout, distributing the structure of an HTML Table across multiple components also distributes the responsibilities of layout, which I think is a bad idea. In fact, the more that I think about it, the more I think that the AngularJS solution looks more natural and appropriate.

As I was digging into all of this, and thinking about reusing components in different contexts, I started to realize that ReactJS suffers from another problem that AngularJS doesn't have: lack of structural insight. By this, I mean that when you look at a component element, you have no idea what kind of structure it's going to generate without looking into the implementation of the component.

For example, take a look at the following JSX markup:

<div className="foo">

<Widget />

</div>

<div className="bar">

<Widget />

</div>

Now, imagine that in the "div.foo" context, I want the Widget to be "display:inline" and in the "div.bar" context, I want the Widget to be "display:block". In ReactJS, this is a difficult problem because the "Widget" component doesn't result in a "widget" element tag. In fact, there's nothing about the component name that indicates what type of structure will be rendered or what CSS classes it will have applied. As such, there's basically no way to provide contextual overrides without knowing the implementation of the component's render() method.

You could pass-in CSS classes or inline styles to the Widget component, providing an override hook. But, even so, doing this requires that the Widget component apply the injected classes and styles, which it won't by default - you have to program that into the DNA of the component's rendering algorithm.

In AngularJS, this type of situation is rarely a problem because you're always working with HTML. Even if you use a custom tag directive, it still results in direct access to the rendered element, to which you can apply additional CSS classes or contextual CSS rules.

It's always fascinating to see what fruit will be yielded from seemingly redundant exploration. Taking the concept of layout decoupling and applying it in a ReactJS context makes me think not only about the structure of my ReactJS component but, also, about the limitations that are inherent with the ReactJS virtual DOM.

Reader Comments

Interesting points. But I wouldn't say that the Angular approach is better. The contents of the Widget should be concealed, using HTML kind of defeats the purpose of that abstraction.

The better solution (like any HTML element) is to supply the top level style. If you use something like Radium you can use styles inline in the react component and pass the layout behavior down as a property from the parent. This then matches the behavior of HTML.

I am not sure if I agree that passing in styles is a better solution than being able to supply contextual CSS properties. That feels like a slippery slope. If you start doing that for some things, how can you use CSS for other things.

For example, what about the context in which you want the top/bottom item in a collection to have a different margin. In CSS, this is easy with something like:

div.parent div.child:last-child { margin-bottom: 0px ; }

To argue that passing-in style is a better approach means that you have to include conditional inline styles based on the index of the element in the current collection.

Not to say you can't do that. But, at some point, it feels like you're defeating the purpose of CSS. Though, it does feel like there is a growing sentiment of anti-CSS methodologies that prefer inline styles over linked stylesheets.

To be honest, I'm not so good at CSS that I can argue for or against the shift in approaches.

There are lots of issues with CSS though. Like class scope, ordering and performance. From an architectural perspective, I wouldn't want to rely on the implementation of a widget and assume that layout behavior will remain consistent. I would much prefer the widget to behave like any other HTML element and expose a standard set of properties that govern its layout behavior.

>> behave like any other HTML element and expose a standard set of properties that govern its layout behavior.

I was under the impression that HTML elements worked the same way, in that they use a native browser Stylesheet to define default layout behaviors. Then, you can override those with addition stylesheets / style attributes. And, if you inspect the shadow DOM in Google Chrome (caveat: I know next to nothing about the shadow DOM), it seems to also make use of stylesheets.

By 'behaving like regular HTML elements' - I just mean that you can set the style of a regular HTML element to modify the layout behavior. You can change the display style property, or use flex and you expect the element to adjust accordingly. These are essentially ambient properties used by the layout engine on all elements.

This approach is better than looking into the implementation of the element so you know how the top-level DIV is styled. If your layout is implementation dependent it is subject to changes in that implementation.

I had this problem recently with a component that I wanted to use in a flex based layout and also with a percentage height. I could have used a CSS class based approach, but then this opens the door to the other issues with CSS. It's better to expose the necessary style properties and control everything with properties, that's more 'the react way'. Plus, this lets me use the higher level components with React Native, which only uses inline styles.

Ah, interesting - I don't know anything about React Native, except for what I've heard on podcasts. I can definitely see the advantage of using inline styles for a lot of things - CSS can get tough to keep in your head, especially with all the cascading based on precedence.

CSS is one of those things I am hoping to bone up on. I'm still mostly a CSS 2.0 person :)

I am the co-founder and lead engineer at InVision App, Inc — the world's leading prototyping,
collaboration & workflow platform. I also rock out in JavaScript and ColdFusion 24x7 and I dream about
promise resolving asynchronously.