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 theInput
-
labelText
: The text for theLabel
-
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 Dropdown
?
-
labelText
: The text for theLabel
-
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/Province
s or Make
s, 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 ComponentCreating Your First PageHow Dependencies WorkTodo App Example
Written by Joseph Dalrymple
Related protips
3 Responses
Good Article bro!
It is works on C+, or I need another things
Nice Article! It was worth Reading!