question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Select] Improve generality of Select

See original GitHub issue

Let’s look at an example use of of Select to illustrate a problem.

Say I have an array of objects like this that I’d like to use in my Select component:

const locations = [
  {id: 4, info: {city: 'San Francisco', state: 'CA', someHash: '123' } }
  {id: 5, info: {city: 'San Francisco', state: 'CA', someHash: '456' } }
]

I want to display the items, combining together city and state, and pass along someHash to my onChange event handler whenever it changes. So I write this in my app:

const SelectLocation = ({onChange}) => {
  const options = locations.map(location => 
         ({ label: location.info.city + ', ' + location.info.state , value: location.id }));

  return (<Select 
      options={options}
      labelKey="label"
      valueKey="value"
      onChange={ ({value}) => locations.find(location => location.id === value).someHash }
      />);
}

Because Select needs a flat object to read the labelKey and valueKey from, I’ve added an O(n) operation and to create a new array of objects in each render. And because we only pass the value, I now have to do an O(n) lookup to find my object. (Actually, it looks like Select passes the whole object. But the types for the component do not reflect this.)

In fact, this is an issue any time you want to show a select of nested objects, or objects without certain existing properties!

How can we improve this? Let’s make Select totally agnostic to what the user passes in.

const SelectLocation2 = ({onChange}) => (
<Select
  options={locations}
  label={location => location.info.name + ', ' + location.info.state}
  onChange={(location) => onChange(location.info.someHash)} />)

Not we only have to call the label fn when we actually render that option. Select can also cache the results of this fn as a possible performance improvement.

Currently, the type of Select’s Props is:

export type OptionT = $ReadOnly<{
  id?: string | number,
  label?: React.Node,
  ...
}>;

type PropsT = {
  onChange: ({value:  OptionT}) => mixed, 
  options: $ReadOnlyArray<OptionT>,
  value: OptionT,
  valueKey: string,
  labelKey: string
}

Even if Select can currently accept objects of any type, Flow will give us the wrong object shape.

The type for Select should be this (note the type param for Option rather than a hard-coded shape):

type PropsT<Option> = {
  onChange: ({value:  Option}) => mixed,
  label: Option => React.Node,
  options: $ReadOnlyArray<Option>,
  value: Option
}

Implementing this would be a breaking change, but you could make upgrading easier by providing a façade like this:

type LegacyPropsT<Option: {}> = {
  options: $ReadOnlyArray<Option>,
  valueKey: $Keys<Option>,
  labelKey: $Keys<Option>,
  onChange: ({value: Option}) => mixed
}
const LegacySelect = <Option: {}>({options, id, label}: LegacyPropsT<Option>) => {
  return <NewSelect 
    id={p => p[id]}
    label{p => p[label]} />
}

We can even improve the accuracy of the types of the legacy wrapper by making it parametric, restricting valueKey and labelKey to the keys that exist on options

This is a problem with Select, but it’s also a problem for any other Base Web component that takes in a list of options. This same pattern (making the list of options parametric) would improve these components as well.

This API has a couple other benefits, notably solving some issues with how StatefulSelect deals with mapping the initialState value to an option. The new type of StatefulSelect’s props could be:

type NewStatefulSelectProps<Option> = {
  label: Option => React.Node,
  initialState: { value: Option },
  ....
}

We can deal with the identity of the option directly rather than have to map back and forth between option and valueKey.

I want to get some feedback on this proposed API change, but I’d be happy to help make these changes if we can agree that they’re beneficial.

Edit: I see newer versions of baseweb’s Select have getOptionLabel and getOptionValue, which are sort of in the correct spirit, but also have overly-specialized types. So maybe this issue should be about fixing the types, and removing valueKey and labelKey (since they artificially restrict the option param, and cause the mapping issue for StatefulSelect).

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:3
  • Comments:10 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
btfordcommented, Oct 26, 2019

This is not stale! I have a PR incoming

0reactions
gergelykecommented, Jan 15, 2020

Closing for now - please feel free to reopen if you plan to work on this! 😃

For reference, we’ve split the Select component since the creation of this ticket, and added a warning for value prop misuse: https://github.com/uber/baseweb/commit/1e6d1276645523b5e2020297668194f83cc551d5

Read more comments on GitHub >

github_iconTop Results From Across the Web

Improving Generalization in Multimodal Sentiment Analysis
In this paper, we propose a Select-Additive Learning (SAL) procedure that improves the generalizability of trained neural networks for ...
Read more >
Lecture 9: Generalization
When we train a machine learning model, we don't just want it to learn to model the training data. We want it to...
Read more >
a study on GitHub ecosystem | SpringerLink
Improving generality and accuracy of existing public development project selection methods: a study on GitHub ecosystem.
Read more >
Improving Generalization via Attribute Selection on Out-of-the ...
This letter first derives a generalization error bound for ZSL tasks. Our theoretical analysis verifies that selecting the subset of key ...
Read more >
Feature Selection to Improve Generalization of Genetic ...
Feature selection, as a data preprocessing method, can potentially contribute not only to improving the efficiency of learning algorithms but ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found