Notes on setting up a React selection list
posted 2021.09.21 by Clark Wilkins, Simplexable

While this post won't be “revolutionary”, perhaps it will help shed some light on both efficient use of noSQL databases (in particular, DynamoDB), and on how to use Stateless Functional Components (SFCs) to make a dynamically loaded selection list. The goal here is to create a selection list of available manufacturers.

Setting up the data

Initially, the source table was using the manufacturer name and the part number as a partition key and sort key, respectively. This meant we could identify and segregate a unique record with this unique pair. It worked fine until it came time to get the list of manufacturers in the database. Most of the answers I found on the internet suggested adding a shadow field with the same content (manufacturer name), and creating a Local Secondary Index. This would generate the desired list, and even allow for proper sorting of the names, but at a rather large storage cost when you consider the possibility of 100K+ part number records.

To make things more efficient, I reorganized this into two tables. First, I created healthica-manufacturers with a partion key id consisting of a v4 UUID and the manufacture name as the payload. In the (lovely to ponder) scenario of 1000 manufacturers, at ~ 50-150 bytes/record, this is not a large query when running a scan (which we do below).

Second, the main table healthica-parts was redone with the partition key manufacturer now using the UUID instead of the name. So now, we can get the unique manufacturer names from a much smaller data set.

This query was packaged into a simple API call which doesn't even need any parameters, since it just pulls the entire id:name list of manufacturers.

Defining access methods before setting up the selector

The selector needed a list of manufacturers (id:value pairs) as content, so I went to work on that first. In my React project, top-level, I added a line in config.json defining getManufacturers as the API access point.

Then I went into the general handler and defined a new function:

export async function getManufacturersList () {
  const { data: manufacturers } = await http.get(getManufacturers);
  return manufacturers;
}

Now, I had access to the manufacturers list. Time to build a selection list.

Feeding the data into a SFC selection list

In a case like this, the form is going to have two inputs, both of which needing to be stored in “state”. This dictates the use of a Stateless Functional Component (SFC), so you can add in a selector with no local state that actually feeds its value back to the parent (our search form) via an onChange event. I already had the SFC defined like this:

import React from "react";
import Select from "react-select";
const Selector = ({
name,
label,
options,
placeHolder,
onChange,
autoFocus,
value,
error,
}) => {
return (
<div className="form-group">
<label htmlFor={name}>{label}
<Select
options={options}
placeholder={placeHolder}
id={name}
name={name}
onChange={onChange}
autoFocus={autoFocus}
value={value}
/>
{error && <div className="alert alert-danger">{error}</div>}
</div>
);
};
export default Selector;

Plugging this in is straightforward. On the form itself:

{manufacturerList && this.renderSelector(
"manufacturer",
"manufacturers",
manufacturerList,
"select a manufacturer here...",
false,
this.handleSelector
)}

The function thisRendorSelector as supplied by the form logic:

renderSelector(
name,
label,
options,
placeHolder,
autoFocus,
onChange,
value
) {
const { errors } = this.state;
return (
<Selector
name={name}
id={name}
label={label}
value={value}
options={options}
placeHolder={placeHolder}
onChange={onChange}
error={errors[name]}
autoFocus={autoFocus}
/>
);
}

The onChange event is being passed to handleSelector. It picks up the new value and stores it in state.data. Also, the data is validated (@hapi.joi), and any errors are stored in state.errors. This is the critical part. The state change is triggering a rendering of the form, and also storing the current values for a submit.

if (selector.__isNew__) {
data[currentTarget.name] = selector.value;
} else {
data[selector.name] = selector.value;
}
this.setState({ data, errors });

The last piece of the puzzle was to alphabetically sort the manufacturer names. Each selector value is actually a three-part object ( label (shown on the list), value, and name (this input object).

async componentDidMount() {
const manufacturers = await getManufacturersList();
this.setState({ manufacturers });
}
...
render() {

const { manufacturers } = this.state;

if ( manufacturers ) {

var manufacturerList = [];
Object.entries( manufacturers ).map(function( key, value ) {
var {id: value, name: label } = key[1];
manufacturerList.push( { label, value, "name": "manufacturer" } );
});
manufacturerList.sort((a,b) => (a.value > b.value) ? 1 : ((b.value > a.value) ? -1 : 0))

}

Running this down:

  1. Once the component has mounted, get the manufacturers list. Dump it into state to force re-rendering.
  2. When the render sees manufacturers, first it initializes an array manufacturerList.
  3. Object.entries creates an array of objects containing the manufacturer ID and name.
  4. The id and name are extracted as value and label respectively.
  5. A three-value JSON string is evaluated as (alphabetically before or after) the other keys to sort the entire array. Since this string leads with the actual manufacturer name, the array is now sorted by the manufacturer name.

This sorted array is fed back into the SFC as the ID:name pairs that make up the selection list, and that's how we get a dynamically generated manufacturer selection list.