Search code examples
ruby-on-railsreactjsreact-rails

how to avoid key/id problems in reactjs and make props pass from parent to child?


I keep hitting a wall when trying to get the parent data passed down to the child component.

My view:

<%= react_component 'Items', { data: @items } %>

My Items component makes an ajax call, sets state, and renders Item. Leaving key={this.props.id} out of the Item instance passed into the mapping function makes it so that the component html renders to the page. But add the key in, and I get a console error: Uncaught TypeError: Cannot read property 'id' of undefined

Here's 'Items':

var Items = React.createClass({
    loadItemsFromServer: function() {
        $.ajax({
            url: this.props.url,
            dataType: 'json',
            cache: false,
            success: function(data) {
                this.setState({data: data});
            }.bind(this),
            error: function(xhr, status, err) {
                console.error(this.props.url, status, err.toString());
            }.bind(this)
        });
    },
    componentDidMount: function() {
        this.loadItemsFromServer();
    },
    render: function() {
        var itemNodes = this.props.data.map(function() {
            return (
                <Item key={this.props.id} />
            );
        });
        return (
            <div className="ui four column doubling stackable grid">
                {itemNodes}
            </div>
        );
    }
});

My item.js.jsx component just formats each Item:

var Item = React.createClass({
    render: function() {
        return (
            <div className="item-card">
                <div className="image">

                </div>
                <div className="description">
                    <div className="artist">{this.props.artist}</div>
                </div>
            </div>
        );
    }
});

The React dev tools extension shows the props and state data inside Items. The children, however, are empty.

enter image description here

I'm aware of this, but I'm setting key with this.props.id. I'm not sure what I'm missing?


Solution

  • I found a couple of problems with the code you posted, in the Items component

    1. You're rendering this.props.data while in fact this.state.data is the one being updated with the ajax request. You need to render this.state.data but get the initial value from props
    2. The map iterator function takes an argument representing the current array element, use it to access the properties instead of using this which is undefined

    The updated code should look like this

    var Item = React.createClass({
        render: function() {
            return (
                <div className="item-card">
                    <div className="image">
    
                    </div>
                    <div className="description">
                        <div className="artist">{this.props.artist}</div>
                    </div>
                </div>
            );
        }
    });
    
    var Items = React.createClass({
        getInitialState: function() {
            return {
                // for initial state use the array passed as props,
                // or empty array if not passed
                data: this.props.data || []
            };
        },
        loadItemsFromServer: function() {
            var data = [{
                id: 1,
                artist: 'abc'
            }, {
                id: 2,
                artist: 'def'
            }]
            this.setState({
                data: data
            });
    
        },
        componentDidMount: function() {
            this.loadItemsFromServer();
        },
        render: function() {
            // use this.state.data not this.props.data, 
            // since you are updating the state with the result of the ajax request, 
            // you're not updating the props
            var itemNodes = this.state.data.map(function(item) {
                // the map iterator  function takes an item as a parameter, 
                // which is the current element of the array (this.state.data), 
                // use (item) to access properties, not (this)
    
                return (
                    // use key as item id, and pass all the item properties 
                    // to the Item component with ES6 object spread syntax
                    <Item key={item.id} {...item} />
                );
            });
            return (
                <div className="ui four column doubling stackable grid">
                    {itemNodes}
                </div>
            );
        }
    });
    

    And here is a working example http://codepen.io/Gaafar/pen/EyyGPR?editors=0010