As we saw in the previous article, decorators are just the Higher-Order components that add extra functionality to the passed component and return a new enhanced component. Before we get to our homework, let’s dive deep and understand how decorators and mixins work.
What are decorators?
Decorators provide a way of calling Higher-Order functions. What are the Higher-Order functions? They simply take a function, modify it and return a new function with added functionality. The key here is that they don’t modify the original function, they simply add some extra functionality which means they can be reused at multiple places. This special feature makes decorators so powerful.
Note: As decorators are not currently part of JavaScript, the below code snippets cannot be executed in the browser. You can use JSFiddle to execute and understand the below code snippets. In JSFiddle, you have to select the language as Babel + JSX as shown below: https://jsfiddle.net/iamshadmirza/62wgxcdz/1/
Let’s understand decorators with the help of an example:
/* Let's say we have a room and we want to decorate it with some cool stuff It can be written as a function in javascript Let's create a poster */ function poster(target){ target.poster = "Avengers"; } //Let's add some wallpaper function wallpaper(target){ target.wallpaper = "Vinyl" } //Let's decorate our room //You just have to add the decorator at top of the class @poster @wallpaper class MyRoom {} console.log(`My room is decorated with an ${MyRoom.poster} poster and has beautiful ${MyRoom.wallpaper} wallpaper`); /* My room is decorated with an Avengers poster and has beautiful Vinyl wallpaper */ //Let's assume we want to add owner name also. decorator for that can be created as function owner(name){ return function(target){ target.owner = name; } } //Then MyRoom can be defined as @poster @wallpaper @owner("Shad") class ShadsRoom {} //Let's check if it's working console.log(`${ShadsRoom.owner}'s room is decorated with ${ShadsRoom.poster} poster and has beautiful ${ShadsRoom.wallpaper} wallpaper`);
Homework
Now we can continue with the homework and create a decorator that adds functionality to open one article and close the rest. Let’s create a new file src/decorators/oneOpen.js
import React, { Component as ReactComponent} from 'react' export default (Component) => class OneOpen extends ReactComponent { state = { openItemId: null, }; openItem = openItemId => ev => { if (ev) ev.preventDefault(); this.setState({ openItemId }); } toggleOpenItem = id => ev => { if (ev) ev.preventDefault() this.setState({ openItemId: id === this.state.openItemId ? null : id }); } isItemOpen = id => this.state.openItemId === id render() { return ( <Component {...this.props} isItemOpen={this.isItemOpen} openItem={this.openItem} toggleOpenItem={this.toggleOpenItem} /> ); } }
As we discussed earlier in the decorator example, we wrap can any component and decorate it with new functionality which allows us to reuse our code. For example, toggleOpen functionality can be used with comments, authors and other places too. Let’s add this decorator to the ArticleList component.
Using Decorators
Decorators use a special syntax in JavaScript. They are placed immediately before the code being decorated and prefixed with an @ symbol.
Note: at the time of writing, the decorators are currently in the “Stage 2 Draft” form, meaning that they are mostly finished but still subject to changes.
Let’s use the decorator in the ArticleList component.
1. First, install babel-plugin-transform-decorators-legacy:
This is important to use the latest syntax for decorators.
npm install --save-dev babel-plugin-transform-decorators-legacy
2. Modify ArticleList:
import React, { Component } from 'react'; import Article from './Article'; import oneOpen from './decorators/oneOpen'; // add decorator @openOpen class ArticleList extends Component { renderListItem = () => { const { articles, isItemOpen, toggleOpenItem } = this.props; return articles.map((article) => ( <li key={article.id}> <Article article={article} isOpen={isItemOpen(article.id)} openArticle={toggleOpenItem(article.id)} /> </li> )); }; render() { return ( <div> <h1>Article list</h1> <ul> {this.renderListItem()} </ul> </div> ); } } export default ArticleList;
Alternate way:
If you want to go with the traditional decorator pattern as function wrapper, you can simply do:
// imports and class code above export default oneOpen(ArticleList);
Remove @oneOpen above class and wrap class with the decorator. That’s it.
Mixins
Mixins are another way of sharing code between multiple components. The commons functionalities like openItem, toggleOpenItem, etc can be extracted to mixins and reused at different places.
Create a file src/mixins/oneOpen.js
export default { getInitialState() { return { openItemId: false } }, openItem(openItemId) { return ev => { if (ev) ev.preventDefault() this.setState({openItemId}); } }, toggleOpenItem(id) { return ev => { if (ev) ev.preventDefault(); this.setState({ openItemId: id === this.state.openItemId ? null : id }); } }, isItemOpen(id) { return this.state.openItemId === id; } }
Mixins are only compatible with React.createClass and not with the new ES6 class constructor. React.createClass has been moved to a separate module so let’s quickly install that:
npm i create-react-class
Let’s update the ArticleListOld with the mixin we just created:
import React, { Component } from 'react'; import Article from './Article'; import oneOpen from './mixins/oneOpen' import createReactClass from "create-react-class"; const ArticleList = createReactClass ({ mixins: [oneOpen], render() { const { articles } = this.props const listItems = articles.map((article) => <li key={article.id}> <Article article = {article} isOpen = {this.isItemOpen(article.id)} openArticle = {this.toggleOpenItem(article.id)} /> </li>) return ( <div> <h1>Article list</h1> <ul> {listItems} </ul> </div> ) } }); export default ArticleList;
Note: Mixins are deprecated and replaced with better and more powerful Higher-Order components which are basically decorators. React community encourages that you use HOC instead of mixins at all time. https://reactjs.org/blog/2015/01/27/react-v0.13.0-beta-1.html#mixins
This was to illustrate the use of mixins. We will only use decorators in the future.
Conclusion
Higher-Order Component and decorator patterns are powerful methods to reuse code at different places. Currently, they are widely used and are a replacement for old mixin syntax. You can still create your mechanism for dealing with mixing functionality between components.
You can check out the live playground of today’s lesson in the codesandbox below.
Reference:
- Exploring ECMAScript Decorators: https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841
- ECMAScript proposals: https://github.com/tc39/proposals#stage-2
- Mixins considered harmful: https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html
Please compare our code with yours, and we will go further. Please check this repository for the right commits.
Previous lessons can be viewed here: https://soshace.com/category/javascript/react/react-lessons/
Hello, where can I find previous lessons? Could you add links to the article, please?
https://soshace.com/category/javascript/react/react-lessons/