reactjs

How to create unique keys for React elements?


I am making a React app that allows you to make a list and save it, but React has been giving me a warning that my elements don't have a unique key prop (elements List/ListForm). How should I create a unique key prop for user created elements? Below is my React code

var TitleForm = React.createClass({
    handleSubmit: function(e) {
        e.preventDefault();
        var listName = {'name':this.refs.listName.value};
        this.props.handleCreate(listName);
        this.refs.listName.value = "";
    },
    render: function() {
        return (
            <div>
                <form onSubmit={this.handleSubmit}>
                    <input className='form-control list-input' type='text' ref='listName' placeholder="List Name"/>
                    <br/>
                    <button className="btn btn-primary" type="submit">Create</button>
                </form>
            </div>
        );
    }
});

var ListForm = React.createClass({
    getInitialState: function() {
        return {items:[{'name':'item1'}],itemCount:1};
    },
    handleSubmit: function(e) {
        e.preventDefault();
        var list = {'name': this.props.name, 'data':[]};
        var items = this.state.items;
        for (var i = 1; i < items.length; i++) {
            list.data.push(this.refs[items[i].name]);
        }
        this.props.update(list);
        $('#'+this.props.name).remove();
    }, 
    handleClick: function() {
        this.setState({
            items: this.state.items.concat({'name':'item'+this.state.itemCount+1}),
            itemCount: this.state.itemCount+1
        });
    },
    handleDelete: function() {
        this.setState({
            itemCount: this.state.itemCount-1
        });
    },
    render: function() {
        var listItems = this.state.items.map(function(item) {
            return (
                <div>
                    <input type="text" className="list-form" placeholder="List Item" ref={item.name}/>
                    <br/>
                </div>
            );
        });
        return (
            <div>
                <form onSubmit={this.handleSubmit} className="well list-form-container">
                    {listItems}
                    <br/>
                    <div onClick={this.handleClick} className="btn btn-primary list-button">Add</div>
                    <div onClick={this.handleDelete} className="btn btn-primary list-button">Delete</div>
                    <button type="submit" className="btn btn-primary list-button">Save</button>
                </form>
            </div>
        )
    }
});


var List = React.createClass({
    getInitialState: function() {
        return {lists:[], savedLists: []};
    },
    handleCreate: function(listName) {
        this.setState({
            lists: this.state.lists.concat(listName)
        });
    },
    updateSaved: function(list) {
        this.setState({
            savedLists: this.state.savedLists.concat(list)
        });
    },
    render: function() {
        var lst = this;
        var lists = this.state.lists.map(function(list) {
            return(
                <div>
                    <div key={list.name} id={list.name}>
                        <h2 key={"header"+list.name}>{list.name}</h2>
                        <ListForm update={lst.updateSaved} name={list.name}/>
                    </div>
                </div>
            )
        });
        var savedLists = this.state.savedLists.map(function(list) {
            var list_data = list.data;
            list_data.map(function(data) {
                return (
                    <li>{data}</li>
                )
            });
            return(
                <div>
                    <h2>{list.name}</h2>
                    <ul>
                        {list_data}
                    </ul>
                </div>
            )
        });
        var save_msg;
        if(savedLists.length == 0){
            save_msg = 'No Saved Lists';
        }else{
            save_msg = 'Saved Lists';
        }
        return (
            <div>
                <TitleForm handleCreate={this.handleCreate} />
                {lists}
                <h2>{save_msg}</h2>
                {savedLists}
            </div>
        )
    }
});

ReactDOM.render(<List/>,document.getElementById('app'));

My HTML:

<div class="container">
    <h1>Title</h1>
    <div id="app" class="center"></div>
</div>

Solution

  • There are many ways in which you can create unique keys, the simplest method is to use the index when iterating arrays.

    Example

        var lists = this.state.lists.map(function(list, index) {
            return(
                <div key={index}>
                    <div key={list.name} id={list.name}>
                        <h2 key={"header"+list.name}>{list.name}</h2>
                        <ListForm update={lst.updateSaved} name={list.name}/>
                    </div>
                </div>
            )
        });
    

    Wherever you're looping over data, here this.state.lists.map, you can pass second parameter function(list, index) to the callback as well and that will be its index value and it will be unique for all the items in the array.

    And then you can use it like

    <div key={index}>

    You can do the same here as well

        var savedLists = this.state.savedLists.map(function(list, index) {
            var list_data = list.data;
            list_data.map(function(data, index) {
                return (
                    <li key={index}>{data}</li>
                )
            });
            return(
                <div key={index}>
                    <h2>{list.name}</h2>
                    <ul>
                        {list_data}
                    </ul>
                </div>
            )
        });
    

    Edit

    However, as pointed by the user Martin Dawson in the comment below, this is not always ideal.

    So what's the solution then?

    Many

    Example:

    const generateKey = (pre) => {
        return `${ pre }_${ new Date().getTime() }`;
    }
    
    const savedLists = this.state.savedLists.map( list => {
        const list_data = list.data.map( data => <li key={ generateKey(data) }>{ data }</li> );
        return(
            <div key={ generateKey(list.name) }>
                <h2>{ list.name }</h2>
                <ul>
                    { list_data }
                </ul>
            </div>
        )
    });