简体   繁体   中英

React how to properly call a component function inside render()

I'm building a sidebar menu skeleton using ReactJs and need to understand the way to call a function inside ReactJs render() function.

The code is below:

import React from 'react';

var menuData = require("./data/admin.menu.json");

class SidebarMenu extends React.Component {
  constructor(props) {
    super(props);
    this.state = { expanded: true };
    this.buildItem = this.buildItem.bind(this);
  };

  buildItem(title, ref, icon) {
    return (
      <div className={"item" + this.props.key}>
        <a href={ref}>{title}<i className={"fa " + icon} /></a>
      </div>
    );
  };

  render() {

    return (
      <div>
        {
          menuData.forEach(function (item) {
            this.buildItem(item.title, item.ref, item.icon);

            if (item.hasOwnProperty("submenu")) {
              item.submenu.forEach(function (subitem) {
                this.buildItem(subitem.title, subitem.ref, subitem.icon);
              });
            }
          })
        }

      </div>
    );
  };
}

export default SidebarMenu;

The given code shows the following error:

Uncaught TypeError: Cannot read property 'buildItem' of undefined

How to properly call a function that will render data inside the ReactJs function ?

The this referenced when you try to call this.buildItem() refers to the anonymous function's context, not your React component.

By using Arrow Functions instead of functions defined using the function keyword inside the render() method, you can use this to reference the React component and its methods as desired.

Alternatively, you can use (function () { ... }).bind(this) to achieve the same result. But this is more tedious and the use of arrow functions is preferred.

Below is one solution, using fat arrow, AKA arrow functions:

import React from 'react';

var menuData = require("./data/admin.menu.json");

class SidebarMenu extends React.Component {
    constructor(props) 
    {
        super(props);

        this.state = { expanded: true };

      this.buildItem = this.buildItem.bind(this);
  };

  buildItem(title, ref, icon) {

    return (
      <div className={"item" + this.props.key}>
        <a href={ref}>{title}<i className={"fa " + item.icon}/></a>
      </div>
    );
  };

  render() {

      return (
        <div>
          {
            menuData.forEach(item => {
              this.buildItem(item.title, item.ref, item.icon);

                if (item.hasOwnProperty("submenu"))
                {
                  item.submenu.forEach(subitem => {
                    this.buildItem(subitem.title, subitem.ref, subitem.icon);
                  });
                }

            })
          }

        </div>
      );
  };
}

export default SidebarMenu;

Another solution would be:

  render() {

      return (
        <div>
          {
            menuData.forEach(function (item) {
              this.buildItem(item.title, item.ref, item.icon);

                if (item.hasOwnProperty("submenu"))
                {
                  item.submenu.forEach(function (subitem) {
                    this.buildItem(subitem.title, subitem.ref, subitem.icon);
                  }.bind(this));
                }

            }.bind(this))
          }

        </div>
      );
  };
}

But, IMO, the best solution would be to refactor the code using a component:

import React, {PropTypes, Component} from 'react';

const menuData = require('./data/admin.menu.json');

function MenuItem({key, ref, title, icon, submenu}) {
  return (
    <div className={`item${key}`}>
      <a href={ref}>{title}<i className={`fa ${icon}`}/></a>
      if (submenu) {
        submenu.map((subitem) => <MenuItem {...subitem} />)
      }
    </div>
  );
}

MenuItem.propTypes = {
    key: PropTypes.string,
    title: PropTypes.string,
    ref: PropTypes.string,
    icon: PropTypes.string,
    submenu: PropTypes.array,
};

class SidebarMenu extends Component {
    constructor(props) {
        super(props);
        this.state = {
            expanded: true,
        };
    }

    render() {
        return (
            <div>
                {
                    menuData.map((subitem) => <MenuItem {...subitem} />)
                }
            </div>
        );
    }
}

export default SidebarMenu;

You can add this line:

render() {
    let that = this
    return (

and then instead of this.buildItem use that.buildItem or you may need that.buildItem.bind(that)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM