12

How can I use an object reference as {key} in ReactJS?

I've tried this:

let ruleList = _.map(this.state.rules, function(rule) {
      return <RuleList rule={rule} key={rule} />
    });

but this ends up being printed in the console:

Warning: flattenChildren(...): Encountered two children with the same key, .0:$[object Object]. Child keys must be unique; when two children share a key, only the first child will be used.

Any way to get around this without hacks such as generating IDs for each item?

5
  • does the rule object have any property which is unique? Commented Jul 13, 2015 at 22:51
  • can you provide a sample of how rule object looks like? Commented Jul 13, 2015 at 22:52
  • { rules: [ ... ], collapsed: true, limit: 5000 } Commented Jul 13, 2015 at 22:53
  • I am asking about rule object. Meaning, one item in the rules array. Commented Jul 13, 2015 at 22:55
  • { type: 'block', value: 'a string' } Commented Jul 13, 2015 at 22:56

6 Answers 6

11

I had a similar situation where the object has no unique id.

I ended up generating ids for the items based on object references:

let curId = 1;
const ids = new WeakMap();

function getObjectId(object) {
  if (ids.has(object)) {
    return ids.get(object);
  } else {
    const id = String(curId++);
    ids.set(object, id);
    return id;
  }
}

// Usage
<RuleList rule={rule} key={getObjectId(rule)} />

I know you mentioned that you don't want to generate ids, but I thought I'd share this since it is generic and doesn't depend on any properties in your object.

Sign up to request clarification or add additional context in comments.

Comments

4

I created a lib intended to solve this problem. It's based on the idea suggested by @amann (using a weakMap). It provides a hook, so that the weakMap is created and destroyed with the component.

Have a look to https://www.npmjs.com/package/react-key-from-object

import { useKeyGen } from 'react-key-from-object'

const DogList = () => {
  const keyGen = useKeyGen();

  return (
    <ul>
      {dogs.map((dog) => (
        <li key={keyGen.getKey(dog)}>
          {dog.name}
          -
          {dog.age}
        </li>
      ))
    </ul>
  );
}

Comments

2

Update

Objects cannot be used as keys. React js requires a key to be a string or a number and should be unique.

IMO there are two ways to solve this problem (open to suggestions)

Option 1

Iterate through the array and create a unique index

var rules = data.rules;
for(var i=0;i<rules.length;i++){
  data.rules[i].key = i;
}

Use this key in _.map

let ruleList = _.map(this.state.rules, function(rule) { 
  return <RuleList rule={rule} key={rule.key} />
});

Option 2

Maintain a array of indices of rule objects which are not deleted.

var N = rules.length; 
var arrayOfRules = Array.apply(null, {length: N}).map(Number.call, Number);

When you delete an item remove it using .splice.

The component should look like this

let ruleList = _.map(this.state.rules, function(rule, index) { 
  return <RuleList rule={rule} key={arrayOfRules[index]} />
});

----

Since rule object has no property which is unique and key needs to be unique, add the index parameter that comes in the map method.

let ruleList = _.map(this.state.rules, function(rule, index) { // <--- notice index parameter
  return <RuleList rule={rule} key={index} />
});

5 Comments

this doesn't work. when you delete an item that has something before it, the rule reference gets clobbered up with the previous item's reference.
I am sorry, I didnt quite understand. Can you please explain more ?
if you have [1, 2, 3, 4] and you remove 1, you'll end up with [1, 2, 3] instead of [2, 3, 4].
@Dhiraj, can you provide any reference that keys must be string or number?
0

Perhaps useId() which is part of React can help!

const ids = childrens.map(() => useId());

https://www.npmjs.com/package/react-key-from-object

Comments

0

This violates React rules but who doesn't:D

const useObjectId = (data) => {
  const idRef = useRef(0);
  return useMemo(() => idRef.current++, [data]);
};

Implementation:

const ruleId = useObjectId(rule);

return <RuleList rule={rule} key={ruleId} />

Comments

-4

Why do you insist on using the rule object as the key?

The easiest solution would be to use an index, returned from the underscore map function, instead. Like this:

let ruleList = _.map(this.state.rules, function(rule, index) {
  return <RuleList rule={rule} key={index} />
});

Here is a similar topic related to the map function.

4 Comments

this doesn't work. when you delete an item that has something before it, the rule reference gets clobbered up with the previous item's reference.
Are all the objects different? In that case, using stringify could help. Like this: key={JSON.stringify(rule)}
Index as a key is an antipattern medium.com/@robinpokorny/…
@marizikmund This worked wonders. Was trying to force a child Component to re-render based on a filters Hash changing. This did the trick perfectly. Cheers.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.