Photo by Thomas AE on Unsplash

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 the render 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 👇

--

--

--

Freelance Web Developer 💻

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

A Clean and Simple Approach for Unit Testing in TypeScript or JavaScript

Nuxt.js — Vuex

My goals as a back-end developer via ZURI INTERNSHIP

NodeJS Frameworks Preferred By Developers

Vue 3 — Transitions and Animations

Create a Desktop App with Vue and Electron

For the beginners: The key difference between npm and npx

Improve the Quality of Your JavaScript Code with Functional Programming

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Vlad Sabev

Vlad Sabev

Freelance Web Developer 💻

More from Medium

Redux-A Predictable State Container

How To Write An Integration Test With Jest/RTL

React Redux Toolkit RTK data fetching

Creating custom Context to share state between components in React