Instantly share code, notes, and snippets.

Embed
What would you like to do?
JSX, a year in

Hi Nicholas,

I saw you tweet about JSX yesterday. It seemed like the discussion devolved pretty quickly but I wanted to share our experience over the last year. I understand your concerns. I've made similar remarks about JSX. When we started using it Planning Center, I led the charge to write React without it. I don't imagine I'd have much to say that you haven't considered but, if it's helpful, here's a pattern that changed my opinion:

The idea that "React is the V in MVC" is disingenuous. It's a good pitch but, for many of us, it feels like in invitation to repeat our history of coupled views. In practice, React is the V and the C. Dan Abramov describes the division as Smart and Dumb Components. At our office, we call them stateless and container components (view-controllers if we're Flux). The idea is pretty simple: components can't be concerned with both presentation and data-fetching. I feel like an example might be clearer...

A component like this would be rejected in code review for having both a presentation and data concern:

// CommentList.js
import React from "react";

class CommentList extends React.Component {
  constructor() {
    super();
    this.state = { comments: [] }
  }
  
  componentDidMount() {
    fetch("/my-comments.json")
      .then(res => res.json())
      .then(comments => this.setState({ comments }))
  }
  
  render() {
    return (
      <ul>
        {this.state.comments.map(({ body, author }) =>
          <li>{body}-{author}</li>
        )}
      </ul>
    );
  }
}

It would then be split into two components. The first is like a traditional template, concerned only with presentation, and the second is tasked with fetching data and rendering the related view component.

// CommentList.js
import React from "react";

const Commentlist = comments => (
  <ul>
    {comments.map(({ body, author }) =>
      <li>{body}-{author}</li>
    )}
  </ul>
)
// CommentListContainer.js
import React from "react";
import CommentList from "./CommentList";

class CommentListContainer extends React.Component {
  constructor() {
    super();
    this.state = { comments: [] }
  }
  
  componentDidMount() {
    fetch("/my-comments.json")
      .then(res => res.json())
      .then(comments => this.setState({ comments }))
  }
  
  render() {
    return <CommentList comments={this.state.comments} />;
  }
}

In the updated example, CommentListContainer could shed JSX pretty simply.

  render() {
    return React.createElement(CommentList, { comments: this.state.comments });
  }

Additionally, a Higher-order Component or component with Render Props could help in making container components and stateless components more composeable

What does this have to do with disliking JSX?

When we started doing this, concerns about JSX vanished. Writing "dumb components" feels just the same as Handlebars or ERB templates but with the full power of JavaScript. We realized that it wasn't JSX that bothered us as much as the nagging feeling that components were just smaller balls of mud. For a while, our components were just smaller balls of mud but this pattern helped break the cycle.

I hope that this was a helpful addition to the conversation. I've written about it in slightly more detail here and here. You can also see Jason Bota's talk about how they do this at Facebook.

Cheers!

Michael

Hi Everyone else 👋

You likely got here from a very popular article by Dan Abramov on the topic of container components.

I'm surprised by the ammount of attention this resource has recieved as it's just an extension of a Twitter conversation I was having.

Gists are a terrible medium because I don't get notified on comments. I've taken a little time today to modernize the code example. So it shouldn't look as foreign to newcomers.

If you'd like more up-to-date React recourses, I mantain these sites:

learnreact.com
reactpatterns.com
reactcheatsheet.com

These resources are more closely monitored and updated.

Thanks for the support!

💖 @chantastic

@burabure

This comment has been minimized.

burabure commented May 5, 2015

I like this idea for cases where react is used without a dedicated data management pattern (models, stores, cursors...). but if you're using flux you should fetch asynchronously in actions and push data from stores =)

@BinaryMuse

This comment has been minimized.

BinaryMuse commented May 7, 2015

I think the concept applies just as well when using flux; your CommentsListContainer is aware of flux stores/actions, and passes these down as properties (either values for display or references to functions that call actions for updates) into CommentList. CommentList remains unaware of flux. [Update] The talk by Jason Bota linked to at the bottom of the Gist talks about this technique.

@damianmr

This comment has been minimized.

damianmr commented Jul 8, 2015

I do not write fetching logic or anything related in my views. I make my views small and very precise on what they do. Nevertheless, I think JSX is a horrible monster. I found other people who think like me when I noticed React Templates (http://wix.github.io/react-templates/).

Just look at how clean and easy to read is the React Template syntax when it comes to iterations or ifs.

May be is just a matter of personal taste.

PS: Very interesting links, thank you

@gnapse

This comment has been minimized.

gnapse commented Jul 9, 2015

Thanks for sharing this! This pattern is not just useful for those that disliked mixing markup and logic. It's useful for everyone, and perhaps React.js should evolve in a way that promotes it.

For instance, one thing of React that I keep struggling with, is the need to pass callbacks deep down the components hierarchy. Under this pattern there could be a way to have all callback functions in the container component, and that all its child stateless/dumb components could reference all those callbacks somehow, without having to pass them down explicitly as props. Perhaps a this.callbacks, and declaring the callbacks somehow in the parent container. Hopefully contexts will end-up helping with this, but unfortunately they're still not publicly and officially available.

@sgonyea

This comment has been minimized.

sgonyea commented Aug 12, 2015

Very well said. Thank you for sharing!

@SoundLogic

This comment has been minimized.

SoundLogic commented Sep 10, 2015

"Smart and Dumb Components" sent me down an endless loop of recursion back and forth between that article and this gist!! 😃

@justjacksonn

This comment has been minimized.

justjacksonn commented Oct 1, 2015

I agree with this way of building the UI with React. However, in the case of a smart component that passes props down to several components, it would then fall on the smart component to lay out the dumb components. Is that acceptable? Or do you have one shared/common top level smarter component that then fetches data for several child smart components, each of which then renders any number of dumb components as need be?

@NeXTs

This comment has been minimized.

NeXTs commented Oct 14, 2015

Good idea, thanks

@iam-peekay

This comment has been minimized.

iam-peekay commented Dec 22, 2015

very helpful. thank you for sharing!

@urbanvikingr

This comment has been minimized.

urbanvikingr commented Jan 7, 2016

If you decide to split your components into Data Component and Presentational Component, you might as well write functional stateless components. See example here https://github.com/urbanvikingr/todo.

Example, in my real app, I've a Dashboard that connects to Redux and gets data for several lists. Each list is a Presentational Component (in fact, each list consist of several Presentational Components which all gets data as props from Dashboard). Each Presentational Component is functional stateless component without render and therefore no dependency to React either. It is just JavaScript. Follow the link above for example app.

@dfischer

This comment has been minimized.

dfischer commented Jan 26, 2016

Wouldn't this traditionally be called a ViewController component as the "smart one?"

Actually passive view presenter: http://martinfowler.com/eaaDev/PassiveScreen.html

@tomonari-t

This comment has been minimized.

tomonari-t commented Feb 19, 2016

Awsome! Thank you!

@adamledwards

This comment has been minimized.

adamledwards commented Feb 25, 2016

This is a good practice to follow. Thanks

@Jerry-Lau

This comment has been minimized.

Jerry-Lau commented Mar 8, 2016

One of the Best Practices of Separation of Concerns

@mholubowski

This comment has been minimized.

mholubowski commented Mar 15, 2016

Great gist, thank you!

@laere

This comment has been minimized.

laere commented Apr 2, 2016

Definitely make sense to separate the logic from the presentation.

@jedwards1211

This comment has been minimized.

jedwards1211 commented Aug 12, 2016

@damianmr definitely personal taste, I have huge beefs with the Angular style of React Templates.

  • Code inside string quotes: onKeyDown="(e)=>if (e.keyCode === 13) { e.preventDefault(); this.addCity(); }"
    How is it going to know if custom props of your own elements actually want a string or want code?
  • How a new import syntax is helpful is beyond me: <rt-import name="*" as="utils" from="utils/utils" />
  • I'd rather have a DSL for HTML inside my JS than a DSL for JS inside my HTML

I don't think the if syntax is really that clean either. You can make components that look clean like this:

<If condition={...}>
  <MyComponent ... />
</If>

Though of course that is not as performant as <MyComponent /> gets created even if the condition is falsey. Though I think I've seen a babel plugin somewhere that transforms it into a conditional expression with the desired short-circuit behavior.

Time and time again I've had a knee-jerk bias against something (e.g. Flux, Redux, and state-management in React) only to try it later, fall in love with it, and wonder "what the hell was I thinking?". I think if you just relax and give pure JSX a try you'll find that your brain adjusts and you learn to read it as easily as Angular templates. Careful indentation can help:

<div>
  {someCondition &&
    <MyComponent ... />
  }
</div>

Here's the other thing: JSX has higher-order power that templates lack: you can pass in components or elements as props, return them from functions, etc:

class UsersView extends React.Component {
  render() {
    const {users, UserLink} = this.props

    return (
      <ul>
        {users.map(user => (
          <UserLink key={user.id} user={user}>
            <li>{user.name}</li>
          </UserLink>
        ))}
      </ul>
    ) 
  }
}

const AdminUserLink = ({user, children}) => <Link to={`/admin/users/${user.id}`}>{children}</Link>
const AdminUserList = <UsersView UserLink={AdminUserLink} />

const UserProfileLink = ({user, children}) => <Link to={`/users/${user.id}`}>{children}</Link>
const UserProfileList = <UsersView UserLink={UserProfileLink} />
@amitrai99

This comment has been minimized.

amitrai99 commented Aug 18, 2016

I am still not clear how this is different from model and view that is supported in other ui frameworks like Backbone. The only difference is that React does not enforce this separation whereas others have this built in.

@roshanraj

This comment has been minimized.

roshanraj commented Oct 5, 2016

Nice one. Thank you!

@thedanotto

This comment has been minimized.

thedanotto commented Nov 27, 2016

Sick example brah!

@glebec

This comment has been minimized.

glebec commented Dec 7, 2016

Quite helpful, thank you.

BTW, it seems to me in your CommentList component that you can do away entirely with the constructor as it is only calling super(props) — remove the subclassed constructor and the prototypal constructor for React components will be called directly. Also, I think you missed a this in your reference to renderComment. It's not in scope, it's a prototypal method and needs to come from somewhere:

class CommentList extends React.Component {
  render() { 
    return <ul> {this.props.comments.map(this.renderComment)} </ul>;
  }

  renderComment({body, author}) {
    return <li>{body}—{author}</li>;
  }
}

Of course you may elect to keep the over-ridden constructor around out of habit / so that you can more easily add things to it in the future without having to type it up, but IMHO if the code is entirely redundant then why keep it around. Add it back in later if necessary.

@randallreedjr

This comment has been minimized.

randallreedjr commented Dec 7, 2016

Since this is such a valuable resource, I just want to point out the minor typo of

I lead the charge to write React without it.

which should instead read

I led the charge to write React without it.

Thanks for putting together such a cogent argument!

@egorovli

This comment has been minimized.

egorovli commented Jun 20, 2017

What if I use smart components without an actual component like so:

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { DumbComponent } from '../components';
import { toJS } from '../decorators';
import {
  someSelector,
  anotherSelector,
  someAction,
  anotherAction
} from '../modules/thing';

const mapStateToProps = state => ({
  someProperty: someSelector(state),
  anotherProperty: anotherSelector(state)
});

const mapDispatchToProps = dispatch =>
  bindActionCreators({
    someAction,
    anotherAction
  }, dispatch);

export default withRouter(
  connect(mapStateToProps, mapDispatchToProps)(
    toJS(DumbComponent)
  )
);

I'll have to write some methods on the Dumb component, however:

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';

class DumbComponent extends PureComponent {
  static propTypes = {
    someProperty: PropTypes.bool.isRequired,
    anotherProperty: PropTypes.string.isRequired,
    someAction: PropTypes.func.isRequired,
    anotherAction: PropTypes.func.isRequired
  };

  constructor(props) {
    super(props);
    this.handleSomeElementClick = this.handleSomeElementClick.bind(this);
    this.handleAnotherElementChange = this.handleAnotherElementChange.bind(this);
  }

  handleSomeElementClick(event) { //  <-- I used to have those in my
    event.preventDefault(); //            smart components and pass it
    const { someAction } = this.props; // down, but now it's defined
    someAction(); //                      in the dumb ones
  }

  handleAnotherElementChange(event) {
    const { name, value } = event.target,
      { anotherAction } = this.props;
    anotherAction({
      [name]: value
    });
  }

  // ...
}

export default DumbComponent;

But this way I avoid a lot of code duplication, like I don't have to check properties in propTypes of the smart component and so on. Is it considered wise?

@danny-andrews

This comment has been minimized.

danny-andrews commented Jun 20, 2017

Rad stuff, dude!

@arshabh

This comment has been minimized.

arshabh commented Jun 26, 2017

I have a similar issue as @egorovli. I am also create smart components using connect from react-redux. I dont know how i can pass data from server as props to the presentational component directly. I also do something similar to what he is doing. I agree it is not the best approach as its beats the purpose - so what do i do ?

@morenoh149

This comment has been minimized.

morenoh149 commented Jul 10, 2017

@egorovli @arshabh in my project we promote the redux container to a react component as well. So you end up with two components, one presentational one container, and the container is decorated with redux (mstp, mdtp).

@ozanmuyes

This comment has been minimized.

ozanmuyes commented Aug 9, 2017

Hi @chantastic,

I've just came here from Dan Abramov's Presentational and Container Components article. Thanks for the awesome example.

There is a small thing that I've wanted to add to your example, the key attribute for li element. I, myself, started to learning the React, after reading 'get started' and 'advanced' guides on React's page (especially this one) I stumbled upon the example of yours. I thought, for the first timers like me, it would be really good to see the key points (pun intended) in action.

Regars.

@BeijiYang

This comment has been minimized.

BeijiYang commented Aug 25, 2017

I came from Dan Abramov's Presentational and Container Components article, too. This brilliant example really helps me a lot. Thank you !

@ldfloresgon

This comment has been minimized.

ldfloresgon commented Aug 29, 2017

hi guys, I am reading Learning React from Alex Banks & Eve Porcello, I recommend this book, it's amazing :), in this book there are a lot of references to Dan Abramov , and sorry for the question, but... when a component doesn't need state, what kind of component are you using? Stateless Component or PureComponent?, and why? I am using Stateless Component. Thanks a lot !!!

@borela

This comment has been minimized.

borela commented Dec 23, 2017

A decorator to simplify the separation: https://github.com/borela/presentable

@mickeypuri

This comment has been minimized.

mickeypuri commented Feb 2, 2018

Using Container as a common suffix creates a lot of noise in the code. Have you looked at any other potential shorter suffix? I thought maybe VC for view controller. That said it is nice to try aim for some kind of generic such as Ctrl for controllers in Angular. Just wondering if some such accepted short form exists

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment