Javascript onClick not firing when assigned inside of array.map

- 1 answer

Ad

I have created a webpage that dynamically loads its content with React.js. I'm retrieving an array of objects from a REST api call, and then feeding them into a table. The issue I have is that the onClick assignment from within the array.map doesn't trigger the assigned function.

I believe this is a [this] context issue, but I'm not sure how to resolve it. The [this] in the array.map is not the same [this] outside of the array.map as demonstrated by console.log.

I have created two standalone html files for demonstration. The first contains a statically created object which is calling the onClick function correctly:

https://jsfiddle.net/m1vugyd9/

The second attempts to dynamically load from an array of objects, and does not work:

https://jsfiddle.net/avmbdxte/

I have no idea how long those links stay active for, so if they don't work, I've also included the actual html below. The difference is somewhat highlighted with comments.

Static - works:

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <style>
        table {
            text-align: center;
        }
    </style>
</head>
<body>
    <div id="reactDiv"></div>

    <script src="https://fb.me/react-0.14.3.min.js"></script>
    <script src="https://fb.me/JSXTransformer-0.13.3.js"></script>
    <script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
    <script type="text/jsx">
        var FMap = React.createClass({
            render: function() {
                return (
                    <tr>
                        <td>{this.props.sTable}</td>
                        <td>{this.props.sField}</td>
                        <td>{this.props.dTable}</td>
                        <td>{this.props.dField}</td>
                        <td><a target="_blank" rel="nofollow noreferrer" target="_blank" rel="nofollow noreferrer" href="#" onClick={this.props.mapCountClick}>{this.props.mapCount}</a></td>
                    </tr>
                );
            }
        });

        var Main = React.createClass({

            getInitialState: function () {
                return { mapData: [] };
            },

            editMaps: function() {
                alert("Clicked on map editor");
            },

            render: function () {
                var maps = [
                    {
                        mapID: 1,
                        sourceT: "sT1",
                        sourceF: "sF1",
                        destT: "dT1",
                        destF: "dF1",
                        mapCount: 6
                    },
                    {
                        mapID: 2,
                        sourceT: "sT1",
                        sourceF: "sF2",
                        destT: "dT1",
                        destF: "dF2",
                        mapCount: 2
                    }
                ];



                /////////////////////////////////////////////////////
                // this is the static part that's different from the dynamic part
                var fMaps =
                    <FMap key="1"
                          sTable="sT1"
                          sField="sF1"
                          dTable="dT1"
                          dField="dF1"
                          mapCount="6"
                          mapCountClick={this.editMaps} />;
                // end of difference
                /////////////////////////////////////////////////////



                return (
                    <table width="100%">
                        <thead>
                            <tr>
                                <th>SourceT</th>
                                <th>SourceF</th>
                                <th>DestT</th>
                                <th>DestF</th>
                                <th>MapCount</th>
                            </tr>
                        </thead>
                        <tbody>
                            {fMaps}
                        </tbody>
                    </table>
                );
            }
        });

        React.render(<Main />, document.getElementById("reactDiv"));
    </script>
</body>
</html>

Dynamic - doesn't work:

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <style>
        table {
            text-align: center;
        }
    </style>
</head>
<body>
    <div id="reactDiv"></div>

    <script src="https://fb.me/react-0.14.3.min.js"></script>
    <script src="https://fb.me/JSXTransformer-0.13.3.js"></script>
    <script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
    <script type="text/jsx">
        var FMap = React.createClass({
            render: function() {
                return (
                    <tr>
                        <td>{this.props.sTable}</td>
                        <td>{this.props.sField}</td>
                        <td>{this.props.dTable}</td>
                        <td>{this.props.dField}</td>
                        <td><a target="_blank" rel="nofollow noreferrer" target="_blank" rel="nofollow noreferrer" href="#" onClick={this.props.mapCountClick}>{this.props.mapCount}</a></td>
                    </tr>
                );
            }
        });

        var Main = React.createClass({

            getInitialState: function () {
                return { mapData: [] };
            },

            editMaps: function() {
                alert("Clicked on map editor");
            },

            render: function () {
                var maps = [
                    {
                        mapID: 1,
                        sourceT: "sT1",
                        sourceF: "sF1",
                        destT: "dT1",
                        destF: "dF1",
                        mapCount: 6
                    },
                    {
                        mapID: 2,
                        sourceT: "sT1",
                        sourceF: "sF2",
                        destT: "dT1",
                        destF: "dF2",
                        mapCount: 2
                    }
                ];



                /////////////////////////////////////////////////////
                // this is the part that doesn't work
                var fMaps = maps.map(function (map) {
                    var component = this;

                    return (
                        <FMap key={map.mapID}
                            sTable={map.sourceT}
                            sField={map.sourceF}
                            dTable={map.destT}
                            dField={map.destF}
                            mapCount={map.mapCount}
                            mapCountClick={this.editMaps} />
                    );
                });
                // end
                /////////////////////////////////////////////////////



                return (
                    <table width="100%">
                        <thead>
                            <tr>
                                <th>SourceT</th>
                                <th>SourceF</th>
                                <th>DestT</th>
                                <th>DestF</th>
                                <th>MapCount</th>
                            </tr>
                        </thead>
                        <tbody>
                            {fMaps}
                        </tbody>
                    </table>
                );
            }
        });

        React.render(<Main />, document.getElementById("reactDiv"));
    </script>
</body>
</html>

Working example (thanks pvg!):

https://jsfiddle.net/qLp9uuq3/

Another working example (thanks Matthew Herbst!):

https://jsfiddle.net/09n6xss2/

Ad

Answer

Ad

In the dynamic part of your code: Map() doesn't get the value of this from the component by default, so you need to bind it:

var fMaps = maps.map(function (map) {
  return (
    <FMap key={map.mapID}
      sTable={map.sourceT}
      sField={map.sourceF}
      dTable={map.destT}
      dField={map.destF}
      mapCount={map.mapCount}
      mapCountClick={this.editMaps} />
  );
}.bind(this));

If you used ES6 arrow functions, this would be lexically inherited and you wouldn't have this issue:

var fMaps = maps.map((map) => {
  return (
    <FMap key={map.mapID}
      sTable={map.sourceT}
      sField={map.sourceF}
      dTable={map.destT}
      dField={map.destF}
      mapCount={map.mapCount}
      mapCountClick={this.editMaps} />
  );
});

Considering you are likely already using Babel or some other tool for JSX transform, might be worth looking into doing ES6 transforms as well. Good way to start learning the future!

Ad
source: stackoverflow.com
Ad