0

I am rendering a few data items in a row, separated by bullets, sometimes omitting these strings if they aren't available/applicable.

const items = [
  age !== null && `age ${age}`,
  nationality && countryName(nationality),
  /* ... */
];
return <Text>{items.filter(x=>x).join(' • ')}</Text>;

I now want to make the age number bold, and so I've replaced the string in the array with a fragment:

[age !== null && (<Fragment>age <Text style={styles.ageNumber}>{age}</Text></Fragment>)]

Reassembling it, I can no longer do join, as that only works on strings. Instead, I've interspersed it with string literals,

const intersperse = (xs, sep) => xs.flatMap((x, i) => (i > 0 ? [sep, x] : [x]));
return <Text>{intersperse(items.filter(x => x), ' • ')}</Text>;

Now that I am passing an array to React, rather than combining it in JavaScript, I get the warning that each child in my array needs a key. How do I handle this nicely, other than by raising more warnings by using the index as the key?

Charlie Harding
  • 653
  • 8
  • 22
  • What makes you think using the index as the key will raise more warnings? It's not recommended but completely viable and legal. – Drew Reese Jan 21 '19 at 16:42
  • ^ Quite a few linting tools will complain if you attempt to use an array key as a React key. E.g. https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-array-index-key.md – tombraider Jan 21 '19 at 16:44

2 Answers2

1

You don't need to create array of nodes, instead you can use combination of reduce and Fragment to create final node and just forget about keys.

This technique is especially useful when you don't have data which has unique keys.

Solution here

Edit 5x5m725m84

Ganapati V S
  • 1,571
  • 1
  • 12
  • 23
  • Hi Ganapati, this seems to avoid the warnings, thank you. Is a recursively nested Fragment tree like this likely to make performance worse? – Charlie Harding Jan 21 '19 at 17:06
  • I don't think it will create any performance issues with small list as we are not creating DOM nodes, but it might create performance issue on large list as everything is re-rendered when single element is changed. – Ganapati V S Jan 21 '19 at 17:07
0

If you have no unique identifier in your array, it's simply a matter of creating a unique index of your choice. Personally, I'd do something like this:

const intersperse = (xs, sep) => xs.flatMap((x, i) => (i > 0 ? [sep, x] : [x]));
return <Text key={`user-info-${Math.random()}`}>{intersperse(items.filter(x => x), ' • ')}</Text>;

Naturally, Math.random() does not necessarily give you a unique number but would usually suffice in the majority of cases. However, if you want to be 100% sure you get a unique key, you could use something like Lodash's uniqueId method instead:

import { uniqueId } from 'lodash/uniqueId';

const intersperse = (xs, sep) => xs.flatMap((x, i) => (i > 0 ? [sep, x] : [x]));
return <Text key={uniqueId('user-info-')}`}>{intersperse(items.filter(x => x), ' • ')}</Text>;

Hope this helps.

EDIT:

Simpler solution as discussed in comments: https://jsfiddle.net/jthcys8v/1/

There's no reason to add things to an array as you're just complicating things. In my opinion this is a much cleaner solution. You could expand on this and call other methods to actually render the Age and Nationality elements, e.g.

<div>
  {userInfo.age && this.renderAge()}
  {userInfo.nationality && this.renderNationality()}
</div>
tombraider
  • 1,127
  • 1
  • 11
  • 19
  • Hi @tombraider, this seems to be applying the key at the wrong level. It is the contents of the Text element that need keys, rather than the Text element itself. Additionally, won't using a random number generator lead to unnecessary rerenders? – Charlie Harding Jan 21 '19 at 16:54
  • You're absolutely correct regarding unnecessary re-renders. This method is by no means best-practice for the reasons you've pointed out. There's some good information on that here: https://stackoverflow.com/questions/29808636/when-giving-unique-keys-to-components-is-it-okay-to-use-math-random-for-gener. Regarding the key being applied to the wrong level, you could simply lift and shift the key generation to the correct component. – tombraider Jan 21 '19 at 16:57
  • For lifting the key generation, do you mean replacing the intersperse with `intersperse(items.filter(x => x), ' • ').map(t=>t)}`, or is there a nicer way of doing that? – Charlie Harding Jan 21 '19 at 17:05
  • Do you have any more of the code that you could share, such as where age and nationality are coming from? This doesn't seem like the nicest solution and it's a bit difficult to suggest something without knowing how your data is being passed around. – tombraider Jan 21 '19 at 17:08
  • I'm getting information about a user, so the API returns a user object with name, age, nationality and other fields. Sometimes the information has not been supplied, so it is null. I want to display this information as a subheading, as described – Charlie Harding Jan 21 '19 at 17:15
  • If that's the case, I'd try and avoid adding things to an array and concatenating them as it's just going to complicate things. I'll edit my answer with a simpler solution. – tombraider Jan 21 '19 at 17:29
  • My issue with the solution after the edit is that it does not allow me to include separators between the elements. As I have all the strings on one line, I would like to separate them (with `' • '`). If I include them raw in the code, they will be rendered even when the element is missing. – Charlie Harding Jan 21 '19 at 19:09
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/187068/discussion-between-tombraider-and-charlie-harding). – tombraider Jan 21 '19 at 19:25