znkw-q
Last Updated: September 11, 2018
·
405
· swivelgames

Smart vs Dumb Components: When to use which

DISCLAIMER

This is an old-ish article originally written as an article for the unpublished docs website for a framework I used to develop called "NUI". This is being posted verbatim, and may contain references to Nui. If you're not familiar with it, Nui was an enterprise-level React rapid development framework. Most (not all) of my focus on the project has since shifted to some other node modules I develop and maintain.

That being said, again, this article was never published or extensively peer-reviewed. There may be some missing or outdated info. If you find any, feel free to call it out in the comments!

Smart vs Dumb Components

Whether it's React Components for Redux or Nui apps

Solve The Question of "When" - What's the difference?

Components provide an immense amount of capabilities to developers. The ability to create markup that is directly coupled to its controller has reinvented Front-end Development as a whole. There are plenty of benefits to using Components.

Pro Or Noob

Whether you're a pro or a new-comer to React, sometimes we get carried away. Does everything really have to be a stateful component? Is there an incentive to using some other alternative? What are the performance implications?

Both Smart and Dumb components important tools for writing cleanly-written, high-functioning, and optimized web apps. To answer the questions above we'll talk about the purpose and difference between Smart Components and Dumb Components and explore examples of how to use them properly.

Smart and Dumb Components

So what's the difference?

A Smart Component is any component which manages its own state. When working with Babel or ES6-style React, we've come to know this as any class-like object that extends Component. This includes either React.Component or in our case Nui.Types.Component.

export default Nui => class MyComponent extends Nui.Types.Component {
  render() {
    return (
      <h1>Hello World</h1>
    );
  }
}

(See here for Nui.Types.Component reference)

A Dumb Component can very easily be defined as a stateless component. A stateless component is much more efficient than a stateful one, because it doesn't require as many computer resources to render (memory, CPU, and GPU in terms of graphic-intensive apps).

export default Nui => () => (
    <h1>Hello World</h1>
);

(See here for how exporting and dependencies works in Nui)

But how and when do you use them?

The Key Concept: Contextual Irrelevance

One of the hardest questions to answer has been, "When do you use One vs The Other?" Traditionally, the answer can seldom be reduced to a term or word describing a best-practice or pattern that we can use to implement in almost every scenario. Usually, the answer is long and drawn out, with several "but"s mixed in. This definitely makes it difficult to categorize our components.

Contextual Irrelevance is a unique test for determining whether or not your component should be Smart or Dumb. It comes down to the functionality and role your component plays in your application. Does your component contain functionality or handle data that is Contextually Irrelevant?

In other words, does your component handle information or functionality that is irrelevant to where you use your component?

Let's explore some examples:

  • A TextInput Component: Dumb
  • A Dropdown Component: Smart

Our Poster-Child Dumb Component: TextInput

As we mentioned earlier, Dumb Components are much more efficient. They require less resources and their source code is usually much more simple.

A simple example of a Dumb Component is a custom TextInput component. In most cases, Form Element Components receive their values from parent components, so their need to keep track of what to display and what values are selected is alleviated by their controlling component.

Our TextInput component will be an Input component paired with a Label component. For this, we'll need to expect a few props:

  • value: The value for the Input
  • labelText: The text for the Label
  • onChange: A function that we'll execute whenever someone types

Straightforward enough, right? Now let's look at what our code might look like (our component's composition):

/* File: views/TextInput.jsx */
export default Nui => ({ value, labelText, onChange }) => (
  <label>
    <strong>{labelText}</strong>
    <input type="text" onChange={onChange} value={value} />
  </label>
);

Add some styling and you've got yourself a simple component that will save you some repetition when creating forms for your application.

Everything Is Contextually Relevant
In our dumb component, everything was contextually relevant. This component can be used in many different Forms. Each of our props are specific to how and where we're putting our <TextInput /> at the time. It will have an entirely different value, onChange, or labelText when used for a First Name field in a contact form, then when used for a Username field in a login form. All of the props are Contextually Relevant; that is, they are relevant to the context in which they're placed. "What form is it being used in this time?" determines what props we give it.

A Smart Component: Dropdown

Notice how in the example above, we didn't have any reason to manage state for the component because all the pieces we needed were conveniently supplied to us by the parent component.

However, just because we can get props from our parent component, doesn't mean we always should. Even in cases where we could get state from a parent component, we probably shouldn't. This is where Contextual Irrelevance comes into play.

Let's explore a Dropdown control so we can understand why a Smart Component is better suited in this case.

What do we need to keep track of, like in our TextInput?

  • labelText: The text for the Label
  • options: An array of options the user can choose from.
  • selected: The currently selected option.
  • onChange: A function the we'll execute whenever someone chooses an option.
  • expanded: A boolean we'll use to keep track of whether or not the list is expanded or collapsed.

So let's see what our Smart component's composition might look like:

/* File: views/Dropdown.jsx */
export default Nui => class DropdownComponent extends Nui.Component {
  constructor(props) {
    // Super must be called when extending another class
    super(props);

    // Set our initial State
    this.state = {
      // Preserve any of the state the `super` may have added
      ...this.state,
      // Start the component collapsed
      expanded: false
    };

    // Bind `toggleOptions` so we can use it in props
    this.toggleOptions = this.toggleOptions.bind(this);
  }

  generateOption(option) {
    // Grab the `selected`, and `onChange` props
    // These are props passed to us by the parent
    const { selected, onChange } = this.props;

    let className = '';
    // Is this the currently selected option?
    if (selected === option) {
      // If so, let's give it a unique class for styling
      className = 'selected';
    }

    // Render our option
    return this.renderOption(option, onChange, className);
  }

  renderOption(option, onChange, className) {
    if (!option) return null;

    // Render the individual option as an <a/> element
    return (
      <a
        key={option}
        onClick={() => onChange(option)}
        className={className}
      >
        {option}
      </a>
    );
  }

  renderList() {
    // Grab the `expanded` property from this component's own state
    const { expanded } = this.state;
    // Grab our `options` array and `selected` props
    // These are props passed to us by the parent
    const { options, selected } = this.props;

    // Did the user expand the menu?
    if (expanded === true) {
      // If so, take each `option` the parent passed in
      // And render it using the `renderOption()` method
      return options.map(this.generateOption.bind(this));
    } else {
      // If not, find the `option` matching our `selected` prop
      // Then just render that element
      return this.renderOption(
        options.find(option => option === selected),
        this.toggleOptions,
        'selected'
      );
    }
  }

  render() {
    // Grab the `labelText` prop
    const { labelText } = this.props;

    return (
      <label className="form-dropdown">
        <strong>{labelText}</strong>
        <nav>{this.renderList()}</nav>
      </label>
    );
  }
}

Notice that this component is a little more involved. However, don't forget the determining factor! A component can have robust functionality, and all of it could be Contextually Relevant, making it a Dumb Component!

So what makes a Dropdown Smart?
Specifically the expanded functionality.

Our dropdown may have different options in an Address form where it would be filled with States/Provinces. In such a case, even our onChange would need to call a specific function that might handle the State/Province selected by the user, versus choosing from a list of Makes on a Used Car Search Form.

But regardless of whether or not it is showing State/Provinces or Makes, our Dropdown will always need to be able to expand or collapse the list to allow the user to change its value. This is entirely Contextually Irrelevant. Placing this functionality in the hands of a parent component would be dangerous because every single parent component that uses the Dropdown component would have to copy that code, which is incredibly inefficient and leads to bugs and hard to maintain code.

Contextual Irrelevance for Building Clean, Optimized Webapps

Now you know! Writing components that are easy to maintain, test, and use are all important in making sure your app stays clean and easy to maintain. Remember to use Contextual Irrelevance as a way of determining whether or not your component should be Smart or Dumb.

Nui is a React framework makes it really easy to write clean and easy to maintain apps. Easy enough to use for your personal website, enterprise tested for production apps. It removes a lot of the major boilerplating needed and has optional server-side rendering pre-configured and built-in.

Related Topic Guides

  • Creating Your First Component
  • Creating Your First Page
  • How Dependencies Work
  • Todo App Example