Skip to content

Instantly share code, notes, and snippets.

@giladaya
Created March 30, 2019 16:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save giladaya/27d8a92babeedf9f79e2f9487b4b257f to your computer and use it in GitHub Desktop.
Save giladaya/27d8a92babeedf9f79e2f9487b4b257f to your computer and use it in GitHub Desktop.
React recursive nested selection component
import * as React from 'react';
const NO_SELECT = '';
interface OptionsObj {
[key: string]: OptionsObj | string[]
}
type Options = OptionsObj | string[];
interface Props {
label: string;
description: string;
options: Options;
onChange: (value: string[]) => void;
value: string[];
}
class NestSelect3 extends React.Component<Props, {}> {
// render a select element.
// This is the element for this level of recursion
renderSelect = (options, level, selected, onChange) => {
return (
<select onChange={onChange} value={selected}>
<option key="noselect" value={NO_SELECT}>--- select ---</option>
{options.map(option => (
<option key={option} value={option}>{option}</option>
))}
</select>
)
}
// Extract an array from options tree
getOptionsArr(options) {
let optArr;
if (Array.isArray(options)) {
optArr = options;
} else {
optArr = Object.keys(options);
}
return optArr
}
// change handler for this level's select element
// sends back own value, effectively truncating the path
handleChangeSelf = (ev: React.FormEvent<HTMLSelectElement>) => {
const val = (ev.target as HTMLInputElement).value;
if (val === NO_SELECT) {
this.props.onChange([]);
} else {
this.props.onChange([val]);
}
}
// change handler for sub-levels in the recursion
// adds this level's selection to the path and sends it up
handleChangeChild = (path) => {
this.props.onChange([].concat(this.props.value[0], path))
}
// render when the path is not empty:
// 1) this level's select element
// 2) sub levels with recursion
renderWithPath = () => {
const {value: path, options} = this.props;
return (
<div>
{this.renderSelect(this.getOptionsArr(options), 0, path[0], this.handleChangeSelf)}
<NestSelect3
label=""
description=""
options={options[path[0]]}
onChange={this.handleChangeChild}
value={path.slice(1)}
/>
</div>
)
}
// render when the path is empty (no sub-levels)
renderWithEmptyPath = () => {
const {options} = this.props;
if (
options === undefined ||
options === null ||
(Array.isArray(options) && options.length === 0)) {
return null;
}
return (
<div>
{this.renderSelect(this.getOptionsArr(options), 0, NO_SELECT, this.handleChangeSelf)}
</div>
)
}
render() {
const {label, description, value: path} = this.props;
return (
<div>
<div title={description}>{label}</div>
{
path.length > 0 ?
this.renderWithPath() :
this.renderWithEmptyPath()
}
</div>
);
}
}
export default NestSelect3;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment