19Dec

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.

Edit React_lesson_lesson4

Reference:

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/

435258_8a75_3

 

JavaScript lover working on React Native and committed to simplifying code for beginners. Apart from coding and blogging, I like spending my time sketching or writing with a cup of tea.

2 Replies to “React Lesson 4: Homework. Decorators and Mixins”

  1. Hello, where can I find previous lessons? Could you add links to the article, please?

Leave a Reply