Created
March 30, 2019 16:23
-
-
Save giladaya/27d8a92babeedf9f79e2f9487b4b257f to your computer and use it in GitHub Desktop.
React recursive nested selection component
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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