Constructing a reusable lookup list in React Clark Wilkins, Simplexable 2021.09.28

This post is about making a reusable list of key:value pairs that map a user ID to a name (+ optional company name). It covers several points that I had to work out independently of one another, so this might be useful in your own work. The goal was to be able to get full user information for a unique ID one time (aysnchronous call to a DynamoDB table) and make a list so that we could replace {thisUserId} with {thisUserName} anywhere it appears in any child component on the page rendered by React.

problem 1: asynchronicity and React event timing

Apparently, judging from the prodigious amount of material online, a lot of us struggle with the concepts of Asynchronous Javascript. I knew that my execution flow had to be something like this:

  1. Get the entire record set for the object record that will be displayed.
  2. Build a list of unique {userIds} whether they appear in history (having made a change), comments (having written one), or photos (having uploaded one).
  3. Get the actual user's name and (optional) company for the {userId}.
  4. Create a new key/value pair mapping {userId} => {fullUserName + companyName}.
  5. Pass {this.props.actualUserName} down to Child components where the username was already in place and ready for display (no downstream lookup needed).

The first thing I had to realize was this: you cannot wait until you are rendering the page, and then try to build out this list. It's going to be an asynchronous call to get the names, and the page is not waiting you to get that information back. The work has to be done inside doSubmit() once we get the complete data object, and the list has to set in {this.state} which will trigger a new rendering of the page, where you can push this information down to the child-components (comments, photos, and history), and display it.

To get the unique list of {userIds}, I iterated the comment, photos, and history objects like this:

// define an empty array we are going to populate

var userInfo = []; // this is set as an array so we can push to it

// if there are any comments, push the owner ID for each comment into userInfo

if ( comments ) {

Object.entries( comments ).map( function ( commentObject ) {
userInfo.push(commentObject[1].author);
} );

}

// if there are any photos, push the owner ID for each photo into userInfo

if ( photos ) {

Object.entries( photos ).map( function ( photoRecord ) {
userInfo.push(photoRecord[1].author);
} );

}
// there will always be at least one history record (creation),
// so push the event creators into userInfo


Object.entries( pnHistory ).map( function ( value ) {
userInfo.push(value[1]);
} );

// use ES6 Set() constructor to compress this array into
// a set of unique user IDs

userInfo = [...new Set(userInfo)];

This completes steps 1 and 2.

problem 2: remapping a complex object to a simple {id:name} array

This require a couple of nuances.

const userNames = await Promise.all(
userInfo.map ( async ( userId ) => {
let { company, name } = await getThisUser ( userId );
return { userId, company, name };
}
));

{userNames} is a zero-indexed array, but we will deal with that at render time (next). This completes step 3.

Having created this object, we now push it to trigger a new rendering of the page.

this.setState({ partNumberInfo, userNames });

Down in the rendering section, we extract the state objects needed to create the results page:

const { data, manufacturers, partNumberInfo, userNames } = this.state;

Note that we are getting {userNames} as created above, so now we can do the mapping for step 4. This is part of a conditional block that waits for the presence of {partNumberInfo} which is only going to be created on doSubmit: the submission of a part number and manufacturer for search.

var namesList = {};

for ( var thisArray of userNames ) {

var { company, name: userName, userId } = thisArray;

if ( company ) { userName += ', ' + company; }

namesList[userId] = userName;

}

Running this down:

Now, we have completed step 4!

problem 3: passing the remapped information to the controlled (child) component

We can now pass the actual user information down to the child component. In this example, we are showing all photos found in the original part number record. (Note: {key} is zero-indexed, but unique internal to the map, so we can use it to satisfy React's requirement for a unique key for each element.

{ photos && Object.entries ( photos ).sort().map( ( attributes, key ) =>

(
<div key = {key}>
<Photo
description = {attributes[1].description}
key = {key}
owner={namesList[attributes[1].owner]}
URI={attributes[1].URI}
/>
</div>
)

) }

Running this down:

So we have now accomplished the goal of a reusable names list, defined once and rendered everywhere it's needed.