How to create unique keys for React elements?

Reactjs

Reactjs Problem Overview


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>

Reactjs Solutions


Solution 1 - Reactjs

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 lopping 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 whats the solution then?

Many

  • You can create a function to generate unique keys/ids/numbers/strings and use that
  • You can make use of existing npm packages like uuid, uniqid, etc
  • You can also generate random number like new Date().getTime(); and prefix it with something from the item you're iterating to guarantee its uniqueness
  • Lastly, I recommend using the unique ID you get from the database, If you get it.

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>
    )
});

Solution 2 - Reactjs

It is important to remember that React expects STABLE keys, meaning you should assign the keys once and every item on your list should receive the same key every time, that way React can optimize around your data changes when it is reconciling the virtual DOM and decides which components need to re-render. So, if you are using UUID you need to do it at the data level, not at the UI level.

Also keep in mind you can use any string you want for the key, so you can often combine several fields into one unique ID, something like ${username}_${timestamp} can be a fine unique key for a line in a chat, for example.

Solution 3 - Reactjs

Keys helps React identify which items have changed/added/removed and should be given to the elements inside the array to give the elements a stable identity.

With that in mind, there are basically three different strategies as described bellow:

  1. Static Elements (when you don't need to keep html state (focus, cursor position, etc)
  2. Editable and sortable elements
  3. Editable but not sortable elements

As React Documentation explains, we need to give stable identity to the elements and because of that, carefully choose the strategy that best suits your needs:

STATIC ELEMENTS

As we can see also in React Documentation, is not recommended the use of index for keys "if the order of items may change. This can negatively impact performance and may cause issues with component state".

In case of static elements like tables, lists, etc, I recommend using a tool called shortid.

  1. Install the package using NPM/YARN:

    npm install shortid --save

  2. Import in the class file you want to use it:

    import shortid from 'shortid';

  3. The command to generate a new id is shortid.generate().

  4. Example:

    renderDropdownItems = (): React.ReactNode => { const { data, isDisabled } = this.props; const { selectedValue } = this.state; const dropdownItems: Array = [];

     if (data) {
       data.forEach(item => {
         dropdownItems.push(
           <option value={item.value} key={shortid.generate()}>
             {item.text}
           </option>
         );
       });
     }
    
     return (
       <select
         value={selectedValue}
         onChange={this.onSelectedItemChanged}
         disabled={isDisabled}
       >
         {dropdownItems}
       </select>
     );
    

    };

IMPORTANT: As React Virtual DOM relies on the key, with shortid every time the element is re-rendered a new key will be created and the element will loose it's html state like focus or cursor position. Consider this when deciding how the key will be generated as the strategy above can be useful only when you are building elements that won't have their values changed like lists or read only fields.

EDITABLE (sortable) FIELDS

If the element is sortable and you have a unique ID of the item, combine it with some extra string (in case you need to have the same information twice in a page). This is the most recommended scenario.

Example:

  renderDropdownItems = (): React.ReactNode => {
    const elementKey:string = 'ddownitem_'; 
    const { data, isDisabled } = this.props;
    const { selectedValue } = this.state;
    const dropdownItems: Array<React.ReactNode> = [];

    if (data) {
      data.forEach(item => {
        dropdownItems.push(
          <option value={item.value} key={${elementKey}${item.id}}>
            {item.text}
          </option>
        );
      });
    }

    return (
      <select
        value={selectedValue}
        onChange={this.onSelectedItemChanged}
        disabled={isDisabled}
      >
        {dropdownItems}
      </select>
    );
  };

EDITABLE (non sortable) FIELDS (e.g. INPUT ELEMENTS)

As a last resort, for editable (but non sortable) fields like input, you can use some the index with some starting text as element key cannot be duplicated.

Example:

  renderDropdownItems = (): React.ReactNode => {
    const elementKey:string = 'ddownitem_'; 
    const { data, isDisabled } = this.props;
    const { selectedValue } = this.state;
    const dropdownItems: Array<React.ReactNode> = [];

    if (data) {
      data.forEach((item:any index:number) => {
        dropdownItems.push(
          <option value={item.value} key={${elementKey}${index}}>
            {item.text}
          </option>
        );
      });
    }

    return (
      <select
        value={selectedValue}
        onChange={this.onSelectedItemChanged}
        disabled={isDisabled}
      >
        {dropdownItems}
      </select>
    );
  };

Hope this helps.

Solution 4 - Reactjs

Do not use this return `${ pre }_${ new Date().getTime()}`;. It's better to have the array index instead of that because, even though it's not ideal, that way you will at least get some consistency among the list components, with the new Date function you will get constant inconsistency. That means every new iteration of the function will lead to a new truly unique key.

The unique key doesn't mean that it needs to be globally unique, it means that it needs to be unique in the context of the component, so it doesn't run useless re-renders all the time. You won't feel the problem associated with new Date initially, but you will feel it, for example, if you need to get back to the already rendered list and React starts getting all confused because it doesn't know which component changed and which didn't, resulting in memory leaks, because, you guessed it, according to your Date key, every component changed.

Now to my answer. Let's say you are rendering a list of YouTube videos. Use the video id (arqTu9Ay4Ig) as a unique ID. That way, if that ID doesn't change, the component will stay the same, but if it does, React will recognize that it's a new Video and change it accordingly.

It doesn't have to be that strict, the little more relaxed variant is to use the title, like Erez Hochman already pointed out, or a combination of the attributes of the component (title plus category), so you can tell React to check if they have changed or not.

edited some unimportant stuff

Solution 5 - Reactjs

Let React Assign Keys To Children

You may leverage React.Children API:

const { Children } = React;

const DATA = [
  'foo',
  'bar',
  'baz',
];

const MyComponent = () => (
  <div>
    {Children.toArray(DATA.map(data => <p>{data}</p>))}
  </div>
);


ReactDOM.render(<MyComponent />,document.getElementById("root"));

<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Solution 6 - Reactjs

To add the latest solution for 2021...

I found that the project nanoid provides unique string ids that can be used as key while also being fast and very small.

After installing using npm install nanoid, use as follows:

import { nanoid } from 'nanoid';

// Have the id associated with the data.
const todos = [{id: nanoid(), text: 'first todo'}];

// Then later, it can be rendered using a stable id as the key.
const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
)

Solution 7 - Reactjs

Another option is weak-key: https://www.npmjs.com/package/weak-key

import weakKey from "weak-key";

const obj1 = {a : 42};
const obj2 = {b : 123};
const obj3 = {a : 42};

console.log(weakKey(obj1)); // 'weak-key-1'
console.log(weakKey(obj2)); // 'weak-key-2'
console.log(weakKey(obj3)); // 'weak-key-3'

console.log(weakKey(obj1)); // 'weak-key-1'

Solution 8 - Reactjs

For a simple array of text-strings; I'm trying one of the two ways:
1. encodeURI which is available on both; NodeJS and browser

const WithEncoder = () => {
  const getKey = useCallback((str, idx) => encodeURI(`${str},${idx}`), [])

  return (
    <div>
      {["foo", "bar"].map((str, idx) => (
        <div key={getKey(str, idx)}>{str}</div>
      ))}
    </div>
  )
}

2. window.btoa which is available only in browser.
const WithB2A = () => {
  const getKey = useCallback((str, idx) => window.btoa(`${str}-${idx}`), [])

  return (
    <div>
      {["foo", "bar"].map((str, idx) => (
        <div key={getKey(str, idx)}>{str}</div>
      ))}
    </div>
  )
}

Solution 9 - Reactjs

Depends on the situation, choose a uniqueId creator is ok when you just want render silly items, but if you render items like drag&drop etc and you haven't any uniqueId for each item, I recommend remap that data in your redux, mapper, wherever and add for each item an uniqueId (and not in the render like

With that remapped that you can use that new Id in your Component.

Solution 10 - Reactjs

Here is what I have done, it works for reordering, adding, editing and deleting. Once set the key is not changed, so no unnecessary re-render. One PROBLEM which may be a show stopper for some: it requires adding a property to your object at first render say "_reactKey".

Example for functional component in psuedo TS (ie it won't run in snippets):

interface IRow{

  myData: string,
  _reactKey?:number
  }


export default function List(props: {
    rows: Array<IRow>
}) {
    const {myRows} = props;
    const [nextKey, setNextKey] = useState(100);
    const [rows, setRows] = useState<Array<IRow>|undefined>();


    useEffect(function () {
        if (myRows) {
            for (let row of myRows){
                if (!row._reactKey){
                    row._reactKey = nextKey;
                    setNextKey(nextKey+1);
                }
            }
            setRows(myRows);
        } else if (!rows) {
            setRows([]);
        }
    }, [myRows, columns]);
    
    addRow(){
    
        let newRow = { blah, blah, _reactKey : nextKey};
        setNextKey(nextKey+1);
        rows.push(newRow);
        setRows({...rows});
    
    }
    
    function MyRow(props:{row:IRow}){
        const {row} = props;
        
        return <tr><td>{row._reactKey}</td><td>row.myData</td></tr>
    
    }
    
    return <table>
        <tr><th>Index</th><th>React Key</th><th>My Data</th></tr>
        rows.map((row, key)=>{
            return <MyRow key={row._reactKey} row={row} />
        }
        </table>
        
}
            
    
    
    

Solution 11 - Reactjs

import React, {useState} from 'react';
import {SafeAreaView,ScrollView,StyleSheet,Text,View,Dimensions} from 'react-native';
const {width}=Dimensions.get('window');
function sayfalar(){
  let pages=[]
  for (let i = 0; i < 100; i++) {    
    pages.push(<View key={i} style={styles.pages}><Text>{i}</Text></View>)
  }
   return pages
}
const App=()=>{
  return(
    
    <View style={styles.container}>
    <ScrollView horizontal={true} pagingEnabled={true}>
    {sayfalar()}
    
    </ScrollView>
    </View>
  )
}
const styles = StyleSheet.create({
  container:{
    flexDirection:'row',
    flex:1
  },
  pages:{
    width:width
  }
})
export default App;

Solution 12 - Reactjs

You can use react-html-id to generate uniq id easely : https://www.npmjs.com/package/react-html-id

Solution 13 - Reactjs

The fastest solution in 2021 is to use uniqid: Go to <https://www.npmjs.com/package/uniqid> for more info but to sum up:

  • First in your terminal and your project file: npm install uniqid
  • Import uniqid in your project
  • Use it in any key that you need!
 uniqid = require('uniqid');
                return(
                    <div>
                        <div key={ uniqid() } id={list.name}>
                            <h2 key={ uniqid() }>{list.name}</h2>
                            <ListForm update={lst.updateSaved} name={list.name}/>
                        </div>
                    </div>
                )
            });

Solution 14 - Reactjs

I am using this:

<div key={+new Date() + Math.random()}>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser1775500View Question on Stackoverflow
Solution 1 - ReactjsDhruv Kumar JhaView Answer on Stackoverflow
Solution 2 - ReactjsErez HochmanView Answer on Stackoverflow
Solution 3 - ReactjsDaniel SantanaView Answer on Stackoverflow
Solution 4 - ReactjsDangerOnTheRangerView Answer on Stackoverflow
Solution 5 - ReactjsVinay SharmaView Answer on Stackoverflow
Solution 6 - ReactjsDavGarciaView Answer on Stackoverflow
Solution 7 - ReactjsHellen FiallosView Answer on Stackoverflow
Solution 8 - ReactjsAakashView Answer on Stackoverflow
Solution 9 - ReactjsIsaacMiguelView Answer on Stackoverflow
Solution 10 - ReactjsAstra BearView Answer on Stackoverflow
Solution 11 - ReactjstahayildirmView Answer on Stackoverflow
Solution 12 - ReactjsLaurent KhoudjaView Answer on Stackoverflow
Solution 13 - ReactjsVrisToKView Answer on Stackoverflow
Solution 14 - ReactjsTudor MorarView Answer on Stackoverflow