React: Injecting Component Props with ES7 Decorators (part 1 — @Inject)
This series of articles explores my experiments in using decorators to make writing and maintaining React components more ergonomic (subjectively).
While I’m sure many prefer to stick to more traditional ways of writing components, I often try to bring some features that make sense from one framework to the other — in this case, Preact’s injection of props
and state
into a component’s render
method.
Disclaimer: after reading through this GitHub issue I’ve decided to stop using the
@Inject
decorator in favor of parameter destructuring:
class UserLink extends Component {
...
render({ user } = this.props, { showTooltip } = this.state) {
return (...);
}
}
This works exactly the same as the decorator while having the benefit of not needing to import an external utility function.
I think this article still has some value, and at the very least should help people understand the original problem I was trying to solve. If you’re still interested, keep reading 😉
🤹 Skills
To effectively follow along with this article, you’ll need knowledge of:
- JavaScript — intermediate
- React/Preact— intermediate
⭕ Stateless Component
Let’s start with a regular stateless component — in this case, displaying the link to a user. It accepts an object containing the ID, first name, and last name:
const UserLink = ({ user }) => (
<a
className="userLink"
href={`http://twitter.com/${user.id}`}
title={`${user.firstName} ${user.lastName}`}
>
@{user.id}
<div className="userLink-tooltip">
{user.firstName} {user.lastName}
</div>
</a>
);
If we put a <main></main>
element in the HTML template, we can render the component like this:
ReactDOM.render(
<UserLink user={{ id: 'vdsabev', firstName: 'Vlad', lastName: 'Sabev' }} />,
document.querySelector('main')
);
The component displays the user’s ID, and on hover also shows the full user name in a tooltip. Here’s a CodePen:
🔴 Class Component
As a component evolves, its requirements change. We often rewrite stateless function components to class components and vice versa.
Now our designer comes back with a fresh design idea — instead of on hover, we will show the tooltip when clicking a small button on the side.
To do this, we need internal state and so rewrite the component as a class:
class UserLink extends Component {
state = {
showTooltip: false,
}; toggleTooltip = () => {
this.setState((state) => ({ showTooltip: !state.showTooltip }));
};
render() {
return (
<a
className="userLink"
href={`http://twitter.com/${this.props.user.id}`}
>
@{this.props.user.id}
<div
className="userLink-tooltip"
style={{ opacity: this.state.showTooltip ? 1 : 0 }}
>
{this.props.user.firstName} {this.props.user.lastName}
</div>
<span className="userLink-button" onClick={this.toggleTooltip}>💬</span>
</a>
);
}
}
And here’s another CodePen:
🔨 Refactoring
There are lots of references to this
in the class component’s render
method which make it significantly more verbose. Also, if we decide to move the user from this.props
to this.state
for whatever reason, we would rewrite all those references as well.
To ease the transition between stateless and class component, we can use the neat destructuring assignment syntax. Notice how in the next render
method references to this.props
and this.state
are isolated to the top, while JSX is essentially the same as in the stateless component:
class UserLink extends Component {
...
render() {
const { user } = this.props;
const { showTooltip } = this.state;
return (
<a
className="userLink"
href={`http://twitter.com/${user.id}`}
>
@{user.id}
<div
className="userLink-tooltip"
style={{ opacity: showTooltip ? 1 : 0 }}
>
{user.firstName} {user.lastName}
</div>
<span className="userLink-button" onClick={this.toggleTooltip}>💬</span>
</a>
);
}
}
There’s also an even neater way to destructure an object with nested properties:
render() {
const {
props: { user },
state: { showTooltip },
} = this;
return (...); // Same JSX as before
}
📝 Technically, we can destructure objects on a single line, like this:
const { props: { user }, state: { showTooltip } } = this;
However, Prettier (which I use in every project) insists on formatting nested destructuring as multiple lines.
This already looks better, and yet — could we keep props
and state
destructuring in the signature of the render
method?
⚛ Preact
That is, in fact, exactly what Preact does by default. Example from the docs:
class Link extends Component {
render(props, state) {
return <a href={props.href}>{props.children}</a>;
}
}
Preact calls the component’s render
method with this.props
as the first argument and this.state
as the second, saving us some work!
However, many projects use React instead of Preact, and will continue doing so. And if we used this Preact feature and then switched to React would we really want to rewrite all of our render
methods?
How do we get the same behavior with minimal effort then?
📝 While extending the base
React.Component
class and overriding therender
method seems like an obvious solution at first, you’re likely to bump into weird errors, and the React docs generally encourage using composition over inheritance.Do let me know if you’ve seen someone figure this out!
💉 Inject
While looking for alternatives, I stumbled upon an article, then this set of helper functions, which ultimately lead me to an answer:
const Inject = (...keys) => (target, propertyKey, descriptor) => {
const originalMethod = descriptor.value; if (typeof originalMethod !== 'function') {
throw new SyntaxError(`@Inject can only be used on class methods, not: ${originalMethod}`);
} return {
...descriptor,
value(...args) {
const values = keys.map((key) => this[key]);
return originalMethod.apply(this, [...values, ...args]);
},
};
};export default Inject;
This function modifies a class method to call the original method it was applied on with some predefined properties of the instance object this
.
Here’s a usage example:
class UserLink extends Component {
...
@Inject('props', 'state')
render({ user }, { showTooltip }) {
return (...); // Same JSX as before
}
}
Unlike Preact’s behavior, the Inject
decorator is explicit — it can inject any arbitrary property of the class component at any parameter position. In this case it mimics Preact in that it injects this.props
as the first parameter and this.state
as the second parameter. Then we can destructure them right there in the method signature!
📝 If you’ve ever used Angular, then you know dependency injection, and
Inject
looks a lot like it.
🤙 Trade-offs
The main advantage here is using the Inject
decorator makes render code more flexible. This means we can refactor components fearlessly while keeping our views unchanged and managing props
and state
at the edges of the render
function (i.e. in the method signature).
The main disadvantage is we have to import the Inject
decorator everywhere we use it, and it adds a bit of magic to every component we write. And for every bit of magic we add to our code base, a new developer on the team might have some trouble understanding and using it, even if it’s well documented.
Also, if we’re rewriting an existing Preact code base into React, the decorator isn’t an ideal solution, as we still have to inject props
and state
into the render method of every single component!
Finally, as of the writing of this article, decorators are still a Stage 2 experimental feature, so use them with caution!
🏁 Conclusion
What do you think — would you use something like the Inject
decorator, or stick to more stable and straightforward JavaScript features like assignment with destructuring?
Do you regularly use any other decorators or utility functions when writing React components?
Let me know in the comments 👇