DEV Community

loading...

React Anti-pattern: renderThing

jcutrell profile image Jonathan Cutrell ・4 min read

If you've done much with React, you've probably run into this kind of scenario:

class Tabs extends React.Component {

  constructor(props){
    super(props)
    this.state = {}
  }

  setActiveTab(activeTab){
    this.setState({ activeTab });
  }

  renderTabs(){
    return (
      this.props.tabs.map(tab =>(
        <a onClick={e => this.setActiveTab(tab.id)}
           key={tab.id}
           className={this.state.activeTab == tab.id ? "active" : ""}
        >
          {tab.title}
        </a>
      ))
    )
  }

  render(){
    return (
      <div>
        <p>Choose an item</p>
        <p>Current id: {this.state.activeTab}</p>
        <nav>
          {this.renderTabs()}
        </nav>
      </div>
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

This would be used like this:

<Tabs tabs={[{title: "Tab One", id: "tab-one"}, {title: "Tab Two", id: "tab-two"}]} />
Enter fullscreen mode Exit fullscreen mode

And this works! If this is all you need for this component forever, by all means, stop here!

But if this code will change in the future, you're likely to end up with a confusing and long component.

The first and most obvious refactor smell here is the renderTabs method. A few things are wrong with this.

First, the Tabs component already has a render method. So what's the difference between the Tabs render and the renderTabs methods? In one, we are rendering the list of tabs. In the other, we are adding some context. We see this often with things like filtered lists.

It may be tempting to wrap up this kind of rendering functionality inside the component specifically because the tabs need to share state somehow with the containing context.

Let's think about how we might refactor this to be easier to understand.

P.S. Let's assume you've got some kind of testing strategy. In this case, we're not going to write tests, but if you did, you'd probably want to assert that your list is rendering, and that clicking on your tabs renders out what you want it to render.

Let's start by removing the renderTabs method. This is going to look ugly at first.

class Tabs extends React.Component {

  constructor(props){
    super(props)
    this.state = {}
  }

  setActiveTab(activeTab){
    this.setState({ activeTab });
  }

  render(){
    return (
      <div>
        <p>Choose an item</p>
        <p>Current id: {this.state.activeTab}</p>
        <nav>
          {this.props.tabs.map(tab =>(
            <a onClick={e => this.setActiveTab(tab.id)}
               key={tab.id}
               className={this.state.activeTab == tab.id ? "active" : ""}
            >
              {tab.title}
            </a>
          ))}
        </nav>
      </div>
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

This is actually a perfectly fine component on its own. But in the future, you probably will have other places you want to use the same tab-style button, so let's see if we can make that button shareable.

Let's look at a single tab on its own.

<a onClick={e => this.setActiveTab(tab.id)}
   key={tab.id}
   className={this.state.activeTab == tab.id ? "active" : ""}
>
  {tab.title}
</a>
Enter fullscreen mode Exit fullscreen mode

Now let's make this component a standalone functional component. (In other words, we want the component to take props, but we don't need it to have its own state.)

const TabButton = ({ onClick, active, title, tabId, ...props}) => (
  <a onClick={e => {e.preventDefault(); props.onClick(tabId)}}
    {...props}
    className={active ? "active" : ""}
  >
    {title}
  </a>
)
Enter fullscreen mode Exit fullscreen mode

Now that we have a functional component, we can integrate this back into our original Tabs component.

const TabButton = ({ onClick, active, title, tabId, ...props}) => (
  <a onClick={e => {e.preventDefault(); props.onClick(tabId)}}
    {...props}
    className={active ? "active" : ""}
  >
    {title}
  </a>
)

class Tabs extends React.Component {
  constructor(props){
    super(props)
    this.state = {}
  }

  setActiveTab(activeTab){
    this.setState({ activeTab });
  }

  render(){
    const { tabs } = this.props;
    const { activeTab } = this.state;
    return (
      <div>
        <p>Choose an item</p>
        <p>Current id: {this.state.activeTab}</p>
        <nav>
          {this.props.tabs.map(tab =>(
            <TabButton onClick={this.setActiveTab}
               active={activeTab == tab.id}
               tabId={tab.id}
               key={tab.id}
               title={tab.title}
            />
          ))}
        </nav>
      </div>
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

So what do we really gain here?

  • Removed unnecessary/confusing renderTabs button
  • Created a reusable TabButton component that doesn't rely on any external state
  • No API changes for the Tabs interface
  • Easier to reason about and separate concerns - two smaller components over one larger component.

This example is contrived and small, but you are almost certainly going to find places where renderThing monsters show up.

The refactoring pattern looks like this:

  1. Remove the monster renderThing method by moving that code back into the original render. Stop there if the code is reasonable.
  2. Isolate a subset of the rendered output to create a new component from. (Note that you may be able to move directly to this step and jump over step 1, but I like to move it back into the render method first to see if it makes sense to just leave it there.)
  3. Work to separate what pieces of state can go away. Ideally, shoot for a functional component; however, beware of a vanity functional component, where you keep state that should be in the subcomponent in it's parent so you can make it "functional." This is far worse than having two well-designed stateful components.
  4. Incorporate your new component into your previous component, replacing markup. If you find yourself passing too many things directly into the child component, it's possible that you should have stopped at step #1 and not abstracted the component out at all.

Knowing when to abstract a component or routine out into its own dedicated component can be hard. Sometimes, it's purely preference; there is no one right way. When in doubt, smaller components are easier to reason about, but abstraction should have a purpose.

What other refactoring patterns are you interested in seeing a writeup on? Comment and let me know!

Discussion (13)

Collapse
js2me profile image
Sergey S. Volkov

Agreed with you Jonathan!
Also I want to tell a couple words about coming advantage of separating component on sub components instead of using the one big react component
It gives advantage because rendering of the react component works by comparing previous props and next props inside react engine. And in this way React can easy check what needs to update or not

Collapse
dance2die profile image
Sung M. Kim

I also wanted to mention the optimization aspect of this article's refactor as well as Sergey did.

Once TabButton is wrapped in React.memo or turned into a Pure Component (as a Class Component), then it'd prevent TabButton from rendering unncessarily.

Collapse
mrlopis profile image
Joao L.

I don't totally agree.

The author here did more than just remove the "renderThing" method. He created a new component called TabButton. That is great - improves readability, testability, and probably performance. But is it really any difference to have a function call vs an inline piece of javscript inside the curly brakets? It's still just a function. React doesn't care where you put your map function in order to check for updates.

Collapse
js2me profile image
Sergey S. Volkov

I think yes, we have differences in performance between {someFunc(data)} and <someComp></someComp>
Yeah, you're right about that someComp it is the same function React.createElement but I talked about that inside this function React have ability to compare outer props with current props and after fully compared it will call re-render.
But if we will talk about {someFunc(data)} that will be a bit different between using component because it will try to call check for updates all child components.

Collapse
hlherrera profile image
Hermes L Herrera • Edited

I suggest to develop a component in another fashion.
Instead of array of props, is more functional and reusable, to use an array of children. Component composition.

Ex.


....
....

Render props, is a great powerfull pattern to use in this case.

Collapse
eblairmckee profile image
Blair McKee

Great real world example! Just ran into a really similar issue earlier today and would not have thought to pull it out like that

Collapse
kioviensis profile image
Max Tarsis

Thanks, Jonathan
Good explanation

Collapse
wintercounter profile image
Victor Vincent • Edited

Not using classes (but functional components and hooks) will automatically save you from such solutions.

Collapse
dance2die profile image
Sung M. Kim • Edited

Would you share more how "function components + hooks" can save one from having renderThings?

Collapse
wintercounter profile image
Victor Vincent

What you did there is basically extracting the return value of your renderThings function. The only way with FC to couple that value inside the component is to store it in a local variable which feels illegal enough for me to not to do it. This would also enforce a pattern like const renderThing = () => {} where I would immediately see that it is the component itself and I just have to rename it to Thing.

Collapse
asparallel profile image
AsParallel

Does the react runtime autobind the this context of the tab button function to that of the consuming component?

Collapse
jcutrell profile image
Jonathan Cutrell Author

Good catch! I don’t believe so. Need to update that code.

Collapse
jwankhalaf profile image
Jwan Khalaf

This was great, thank you, Jonathan.

Forem Open with the Forem app