Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at Dan Wilson's 2011 (North Carolina) with: Nick James Wilson
Ben Nadel at Dan Wilson's 2011 (North Carolina) with: Nick James Wilson

Passing Contextual Classes And Styles Into Child Components In ReactJS

By Ben Nadel on

I've been thinking a lot about AngularJS component directives and how they dove-tail with the concepts of the "shadow DOM". With AngularJS, you get this outer shell which, in the vast majority of cases, is the rendered container element. You can add classes to it, style it, reference it, etc. Now, as I was thinking about this, I realized that in ReactJS the "component" isn't the rendered element; rather, it's just an element-representation that knows how to render the virtual DOM. This can make adding context-driven classes and inline styles a bit confusing. Luckily, we can pass class names and styles down to the child components as properties so long as we configure our ReactJS components to consume those properties.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

When we render Divs, Spans, or any other HTML element in ReactJS, we often use the "className" and "style" attributes to apply CSS and inline display properties. But, there's nothing special about Divs and Spans. They are, ultimately, just ReactJS classes. And, there's nothing special about the properties "className" and "style". They are, ultimately, just "props" that a particular set of ReactJS classes knows how to deal with.

The point I'm trying to make is that we can pass "className" and "style" properties into any ReactJS component, Div, Span, or otherwise. And, as long as that component knows how to consume those properties and apply them to the rendered virtual element, we can provide a means for our calling context to supply override styles.

To see what I mean, I've created a simple demo in which I have a Widget component that has a set of default styles. And, by default, it renders an inline-block element. But, in our demo, the calling context wants to define that as an absolutely-positioned element with explicit positioning. To do this, it passes-in a "context className" and a "context style" collection. The Widget component then merges those properties into its virtual representation.

  • <!doctype html>
  • <html>
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Passing Contextual Classes And Styles Into Child Components In ReactJS
  • </title>
  •  
  • <link rel="stylesheet" type="text/css" href="./demo.css"></link>
  • </head>
  • <body>
  •  
  • <h1>
  • Passing Contextual Classes And Styles Into Child Components In ReactJS
  • </h1>
  •  
  • <div id="content">
  • <!-- App will be rendered here. -->
  • </div>
  •  
  •  
  • <!-- Load scripts. -->
  • <script type="text/javascript" src="../../vendor/reactjs/react-0.13.3.js"></script>
  • <script type="text/javascript" src="../../vendor/reactjs/JSXTransformer-0.13.3.js"></script>
  • <script type="text/jsx">
  •  
  • // I manage the root component.
  • var Demo = React.createClass({
  •  
  • // I return the virtual DOM represented by the current component state.
  • render: function() {
  •  
  • var widgetStyle = {
  • left: 50,
  • top: 100
  • };
  •  
  • // While the "Widget" isn't the actual rendered DOM element, it is a
  • // component that can accept "override" className and style properties.
  • // These will be applied to the underlying DOM element at render time.
  • return(
  • <div>
  • <Widget className="for-demo" style={ widgetStyle }>
  • Woot!
  • </Widget>
  • </div>
  • );
  •  
  • }
  •  
  • });
  •  
  •  
  • // --------------------------------------------------------------------------- //
  • // --------------------------------------------------------------------------- //
  •  
  •  
  • // I manage the widget component.
  • var Widget = React.createClass({
  •  
  • // I define the shape of the incoming props requirement.
  • propTypes: {
  • className: React.PropTypes.string,
  • style: React.PropTypes.object
  • },
  •  
  •  
  • // I provide the defaults for props that were desired, but not passed-in.
  • getDefaultProps: function() {
  •  
  • return({
  • className: "",
  • style: {}
  • });
  •  
  • },
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I return the virtual DOM represented by the current component state.
  • render: function() {
  •  
  • // When we render our widget, notice that we are transferring the
  • // incoming className and style props onto our widget. This allows the
  • // calling context to provide overrides for what we would define as
  • // the default styling of the component.
  • return(
  • <div
  • className={ "widget " + this.props.className }
  • style={ this.props.style }>
  •  
  • <span className="label">
  • { this.props.children }
  • </span>
  •  
  • </div>
  • );
  •  
  • }
  •  
  • });
  •  
  •  
  • // --------------------------------------------------------------------------- //
  • // --------------------------------------------------------------------------- //
  •  
  •  
  • // Render the root Demo and mount it inside the given element.
  • React.render( <Demo />, document.getElementById( "content" ) );
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, when the <Demo/> element renders the <Widget/> element, it passes-in a className and a style property which our <Widget/> element is merging into its virtual DOM. Here is the CSS that I am using for the demo:

  • /* By default, this widget is just inline-block. */
  •  
  • div.widget {
  • background-color: #FAFAFA ;
  • border: 1px solid #CCCCCC ;
  • border-radius: 3px 3px 3px 3px ;
  • display: inline-block ;
  • padding: 14px 25px 12px 25px ;
  • position: relative ;
  • }
  •  
  • div.widget span.label {
  • display: block ;
  • }
  •  
  • div.widget:after {
  • background-color: #FF0099 ;
  • border-radius: 7px 7px 7px 7px ;
  • content: "" ;
  • height: 7px ;
  • position: absolute ;
  • right: 7px ;
  • top: 7px ;
  • width: 7px ;
  • }
  •  
  •  
  • /*
  • For the demo, the widget is going to be overridden to be an
  • absolutely-positioned element rather than an inline-block one.
  •  
  • NOTE: We have to provide the DIV otherwise this selector won't be
  • able to override the previous selector that defined the base styles.
  • */
  • div.widget.for-demo {
  • border-width: 2px ;
  • position: absolute ;
  • }

As you can see, the class is providing general override styles where as the inline style property is providing explicit positioning. And, when we run this page, we get the following output:


 
 
 

 
 Passing contextual classes and styles into child elements in ReactJS.  
 
 
 

If all I did was pass-in the className and style properties, nothing would have happened. This only works because the reusable component was set up to accept contextual class names and inline style properties. Of course, that's exactly what the Div, Span, and other "native" ReactJS elements are doing.

On a side-note, I just watched an interesting presentation by Michael Chan on moving a lot of style into your ReactJS components.




Reader Comments

Hey Ben,

how about passing the props via {...this.props} (JSX spread attributes) to the div which is rendered by the Widget?

Best,
David

Reply to this Comment

@David,

Great question; I thought about using that, but my concern was that any arbitrary values would then be passed-down to the rendered component. I'm not saying that this is a bad thing; only, that it might not be what the module author intends.

Of course, the nice thing about the spread-operator is that it won't override local attributes. Just the opposite - the local attributes will override the values supplied in the spread collection. So, even if you were to try to provide an "onClick" property, for example, it may be overridden by an onClick attribute used internally.

So, I guess, long story short, I think the spread operator would work. It just felt a little _too flexible_ :D But, maybe there's no downside to that.

Reply to this Comment

@David,

After thinking on this a bit more, I think there is a downside to the spread operator, which is that it won't aggregate colliding values. Meaning, if the calling context AND the component both need to provide a "style" attribute, for example, it won't merge the objects; rather, the inline one will override anything passed-in via the spread operator. As such, even if you use the spread operator, you'll likely need to manually merge the values anyway.

Reply to this Comment

@David,

I was just watching a presentation on using inline styles in React and, going back to the automatically merged concept, the presentation used some internal merge() method to do this explicitly. And, not only did it do this for the passed-in style, it also used the same thing to merge-in state-based styles as well.

Imagine a button that can have an ON state, the code would look something like (pseudo code):

var buttonStyles = merge(
. . . . this.stateStyles.default,
. . . . this.props.style,
. . . . this.stateStyles.on
);

<button style={ buttonStyles } />

... here, you can see that the merge() method is actually mixing in the default styles, the passed-in styles, and the state-based styles into a single collection.

Anyway, just wanted to pass that on.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.