Seth Feldkamp
Seth Feldkamp

Reputation: 1646

Best Practice: encapsulate components or compose deeply using props.children

I'm faced with a decision about how to compose my components and I'm wondering if the community has any ideas about which is the best practice.

1: Encapsulate components to hide details.

Owner renders:

<List ... />

List renders:

<Item ... />

Item renders:

<div>...</div>

In this version the components aren't very reusable because they know alot about the domain details. The Owner doesn't know anything about an Item in a list.

2: Nest components deeply near the top, stateful view-controllers

Owner renders:

<List>
  <Item>
    <div>...</div>
  </Item>
</List>

In this version, the Owner knows all about the composition of it's children, but the components themselves aren't very useful, because they know nothing about the domain. They are however more reusable.

3: Encapsulate generic components in domain specific components

This is a hybrid of option 1 and option 2

Owner renders:

<DomainList ... />

DomainList renders:

<List ...>
  <DomainItem .../>
</List>

DomainItem renders:

<Item ...>
   ...
</Item>

The Owner knows nothing about the generic List, DomainItem, or generic Item.

Which approach is better? Has anyone had success or failure with any of these approaches? What happened?

Upvotes: 4

Views: 505

Answers (1)

Anders Ekdahl
Anders Ekdahl

Reputation: 22933

Components aren't less reusable just because they dictate most things about how they should be rendered. You could even argue that they are more reusable since the component using it doesn't need to know anything about how it renders.

It's quite easy to get into a paralysis by analysis state when you start to think about these things. My approach to creating React components is:

  1. Try to avoid having state in the component, and pass in the state as props instead from higher up in the hierarchy.
  2. Only use this.props.children for wrapper components that doesn't know about what content goes into it. General stuff like a popup, tab-view, etc.
  3. Don't start extracting stuff into other components because "I might reuse this in some other place later". Instead I only extract once I know that I need it in some other place.
  4. Instead of thinking like the author of that component, I try to think like the user of the component. Ask myself questions like "How would I want to pass data to this component?", "Does the user of this component need to be alerted when something changes in it?", "How much flexibility do I need when using this component?". It's easy to go to far here with generalization though, so I also try not to abstract too much.

I tend to see two different types of components. The general purpose stuff that is either so general that I could reuse them between projects or application specific but general enough that I'll use them a lot in different parts of the application for different purposes. Then there's the specific components that have very specific purposes, things like "render this todo list" or "render this form to edit the todo". These tend to use the general purpose components, but definitely not the other way around.

I usually know pretty well when I start to create the component which of these types it's going to be. And for the general purpose components, I tend to make them as flexible and open as possible. For the specific purpose ones, I make them more like black boxes, sort of like "Just give me the data and I'll take it from there". Because they have very descriptive names and knows all there is to know about how to render themselves, it's easy to understand what's going on when reading code that uses them.

In your example, I'd say that a List component is a general purpose component, and I'd definitely want to make it flexible. I'd probably have a specific component for the list I'm building as well, something like a TodoList that could look something like:

var TodoList = React.createClass({
  render() {
    return (
      <List>
        {this.props.todos.map(todo => <ListItem>{todo.text}</ListItem>)}
      </List>
    );
  }
});

Another option would be something like this:

var ListItem = React.createClass({
  render() {
    return <li>{this.props.item.text}</li>;
  }
});

var TodoList = React.createClass({
  render() {
    return <List items={this.state.items} listItemComponent={ListItem} />;
  }
});

In this case, the List component iterates over all items and uses the passed listItemComponent component to render the items.

TL;DR

Start out by making very specific components that knows a lot, and refactor them to be more general when you see a use case for reuse.

Upvotes: 3

Related Questions