---
title: "React Select Multi level hierarchy Example | Mobiscroll"
component: "Select"
framework: "React"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/react/select/linked-hierarchical-pickers"
---

# Multi level hierarchy - React Select demo

React hierarchical select example. Dynamically populate linked lists based on previous selection. For React and Next.JS.

- [Live demo](https://demo.mobiscroll.com/react/select/linked-hierarchical-pickers)
- [View React JS code on GitHub](https://github.com/acidb/mobiscroll-demos-react/tree/main/src/demos/pickers/select/linked-hierarchical-pickers)
- [View React TS code on GitHub](https://github.com/acidb/mobiscroll-demos-react-ts/tree/main/src/demos/pickers/select/linked-hierarchical-pickers)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/select/linked-hierarchical-pickers.md), [jQuery](https://demo.mobiscroll.com/jquery/select/linked-hierarchical-pickers.md), [Angular](https://demo.mobiscroll.com/angular/select/linked-hierarchical-pickers.md), [Vue](https://demo.mobiscroll.com/vue/select/linked-hierarchical-pickers.md)

## Description

Multiple selects can be linked together as a hierarchy. A region > division > subdivision is a great example of such a hierarchy where each level is dynamically populated based on the selection of the previous level.

## What this demo shows

- Three linked select inputs create a region, division, and subdivision hierarchy.
- **Inputs** The Region, Division, and Subdivision inputs are displayed vertically in hierarchical order.
- **Initial state** The Region input is enabled by default, while the Division and Subdivision inputs remain disabled until the preceding level has a value.
- **Dependent options** Each selection populates the options available at the next level of the hierarchy.
- **Picker interaction** Focusing or clicking an enabled input opens its picker below the input; clicking outside closes it.
- **Picker content** Each picker displays a predefined list of options in a dropdown-style popup.
- **Hover state** Hovering over an option highlights it in the picker.
- **Selection** Selecting an option marks it with a checkmark and updates the associated input value.

## Best for

- **Geographic selection** Guide users through related location levels, such as region, division, and subdivision.
- **Organizational structures** Select a department, team, and role from dependent options.
- **Product catalogs** Narrow a selection by category, subcategory, and product.
- **Vehicle selection** Choose a maker, model, and trim in sequence.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/react/select/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/react/select/api#opt-themeVariant) - option
- [`data`](https://mobiscroll.com/docs/react/select/api#opt-data) - option
- [`touchUi`](https://mobiscroll.com/docs/react/select/api#opt-touchUi) - option
- [`label`](https://mobiscroll.com/docs/react/select/api#opt-label) - option
- [`onChange`](https://mobiscroll.com/docs/react/select/api#event-onChange) - event
- [`disabled`](https://mobiscroll.com/docs/react/select/api#opt-disabled) - option

## Code

### JSX

```jsx
import { Select, setOptions } from '@mobiscroll/react';
import { useCallback, useMemo, useState } from 'react';

setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

function App() {
  const [divDisabled, setDivDisabled] = useState(true);
  const [subDisabled, setSubDisabled] = useState(true);
  const [divData, setDivData] = useState([]);
  const [subData, setSubData] = useState([]);

  const regions = useMemo(
    () => [
      { value: 'reg-1', text: 'Northeast' },
      { value: 'reg-2', text: 'Midwest' },
      { value: 'reg-3', text: 'South' },
      { value: 'reg-4', text: 'West' },
    ],
    [],
  );

  const divisions = useMemo(
    () => ({
      'reg-1': [
        { value: 'div-1', text: 'New England' },
        { value: 'div-2', text: 'Mid-Atlantic' },
      ],
      'reg-2': [
        { value: 'div-3', text: 'East North Central' },
        { value: 'div-4', text: 'West North Central' },
      ],
      'reg-3': [
        { value: 'div-5', text: 'South Atlantic' },
        { value: 'div-6', text: 'East South Central' },
        { value: 'div-7', text: 'West South Central' },
      ],
      'reg-4': [
        { value: 'div-8', text: 'Mountain' },
        { value: 'div-9', text: 'Pacific' },
      ],
    }),
    [],
  );

  const subdivisions = useMemo(
    () => ({
      'div-1': [
        { value: 'sub-1', text: 'Connecticut' },
        { value: 'sub-2', text: 'Maine' },
        { value: 'sub-3', text: 'Massachusetts' },
        { value: 'sub-4', text: 'New Hampshire' },
        { value: 'sub-5', text: 'Rhode Island' },
        { value: 'sub-6', text: 'Vermont' },
      ],
      'div-2': [
        { value: 'sub-7', text: 'New Jersey' },
        { value: 'sub-8', text: 'New York' },
        { value: 'sub-9', text: 'Pennsylvania' },
      ],
      'div-3': [
        { value: 'sub-10', text: 'Illinois' },
        { value: 'sub-11', text: 'Indiana' },
        { value: 'sub-12', text: 'Michigan' },
        { value: 'sub-13', text: 'Ohio' },
      ],
      'div-4': [
        { value: 'sub-14', text: 'Wisconsin' },
        { value: 'sub-15', text: 'Iowa' },
        { value: 'sub-16', text: 'Kansas' },
        { value: 'sub-17', text: 'Minnesota' },
        { value: 'sub-18', text: 'Missouri' },
        { value: 'sub-19', text: 'Nebraska' },
        { value: 'sub-20', text: 'North Dakota' },
        { value: 'sub-21', text: 'South Dakota' },
      ],
      'div-5': [
        { value: 'sub-22', text: 'Delaware' },
        { value: 'sub-23', text: 'Florida' },
        { value: 'sub-24', text: 'Georgia' },
        { value: 'sub-25', text: 'Maryland' },
        { value: 'sub-26', text: 'North Carolina' },
        { value: 'sub-27', text: 'South Carolina' },
        { value: 'sub-28', text: 'Virginia' },
        { value: 'sub-29', text: 'District of Columbia' },
        { value: 'sub-30', text: 'West Virginia' },
      ],
      'div-6': [
        { value: 'sub-31', text: 'Alabama' },
        { value: 'sub-32', text: 'Kentucky' },
        { value: 'sub-33', text: 'Mississippi' },
        { value: 'sub-34', text: 'Tennessee' },
      ],
      'div-7': [
        { value: 'sub-35', text: 'Arkansas' },
        { value: 'sub-36', text: 'Louisiana' },
        { value: 'sub-37', text: 'Oklahoma' },
        { value: 'sub-38', text: 'Texas' },
      ],
      'div-8': [
        { value: 'sub-39', text: 'Arizona' },
        { value: 'sub-40', text: 'Colorado' },
        { value: 'sub-41', text: 'Idaho' },
        { value: 'sub-42', text: 'Montana' },
        { value: 'sub-43', text: 'Nevada' },
        { value: 'sub-44', text: 'New Mexico' },
        { value: 'sub-45', text: 'Utah' },
        { value: 'sub-46', text: 'Wyoming' },
      ],
      'div-9': [
        { value: 'sub-47', text: 'Alaska' },
        { value: 'sub-48', text: 'California' },
        { value: 'sub-49', text: 'Hawaii' },
        { value: 'sub-50', text: 'Oregon' },
        { value: 'sub-51', text: 'Washington' },
      ],
    }),
    [],
  );

  const getData = useCallback(
    (region, division) => {
      let arr = [];

      if (division) {
        arr = subdivisions[division];
      } else if (region) {
        arr = divisions[region];
      } else {
        arr = regions;
      }

      return arr;
    },
    [divisions, regions, subdivisions],
  );

  const regChange = useCallback(
    (event) => {
      setDivData(getData(event.value, null));
      setDivDisabled(false);
      setSubDisabled(true);
    },
    [getData],
  );

  const divChange = useCallback(
    (event) => {
      const val = event.value;
      if (val) {
        setSubData(getData(null, event.value));
        setSubDisabled(false);
      } else {
        setSubData([]);
        setSubDisabled(true);
      }
    },
    [getData],
  );

  return (
    <div className="mbsc-grid mbsc-grid-fixed mbsc-no-padding">
      <div className="mbsc-row">
        <div className="mbsc-col-sm-12">
          <div className="mbsc-form-group-inset">
            <Select data={getData(null, null)} touchUi={false} label="Region" placeholder="Please select..." onChange={regChange} />
            <Select
              data={divData}
              touchUi={false}
              disabled={divDisabled}
              label="Division"
              placeholder="Please select..."
              onChange={divChange}
            />
            <Select data={subData} touchUi={false} disabled={subDisabled} label="Subivision" placeholder="Please select..." />
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;
```

### TSX

```tsx
import { MbscSelectChangeEvent, Select, setOptions } from '@mobiscroll/react';
import { FC, useCallback, useState } from 'react';

setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

const regions: Array<{ value: string; text: string }> = [
  { value: 'reg-1', text: 'Northeast' },
  { value: 'reg-2', text: 'Midwest' },
  { value: 'reg-3', text: 'South' },
  { value: 'reg-4', text: 'West' },
];
const divisions: { [key: string]: Array<{ value: string; text: string }> } = {
  'reg-1': [
    { value: 'div-1', text: 'New England' },
    { value: 'div-2', text: 'Mid-Atlantic' },
  ],
  'reg-2': [
    { value: 'div-3', text: 'East North Central' },
    { value: 'div-4', text: 'West North Central' },
  ],
  'reg-3': [
    { value: 'div-5', text: 'South Atlantic' },
    { value: 'div-6', text: 'East South Central' },
    { value: 'div-7', text: 'West South Central' },
  ],
  'reg-4': [
    { value: 'div-8', text: 'Mountain' },
    { value: 'div-9', text: 'Pacific' },
  ],
};
const subdivisions: { [key: string]: Array<{ value: string; text: string }> } = {
  'div-1': [
    { value: 'sub-1', text: 'Connecticut' },
    { value: 'sub-2', text: 'Maine' },
    { value: 'sub-3', text: 'Massachusetts' },
    { value: 'sub-4', text: 'New Hampshire' },
    { value: 'sub-5', text: 'Rhode Island' },
    { value: 'sub-6', text: 'Vermont' },
  ],
  'div-2': [
    { value: 'sub-7', text: 'New Jersey' },
    { value: 'sub-8', text: 'New York' },
    { value: 'sub-9', text: 'Pennsylvania' },
  ],
  'div-3': [
    { value: 'sub-10', text: 'Illinois' },
    { value: 'sub-11', text: 'Indiana' },
    { value: 'sub-12', text: 'Michigan' },
    { value: 'sub-13', text: 'Ohio' },
  ],
  'div-4': [
    { value: 'sub-14', text: 'Wisconsin' },
    { value: 'sub-15', text: 'Iowa' },
    { value: 'sub-16', text: 'Kansas' },
    { value: 'sub-17', text: 'Minnesota' },
    { value: 'sub-18', text: 'Missouri' },
    { value: 'sub-19', text: 'Nebraska' },
    { value: 'sub-20', text: 'North Dakota' },
    { value: 'sub-21', text: 'South Dakota' },
  ],
  'div-5': [
    { value: 'sub-22', text: 'Delaware' },
    { value: 'sub-23', text: 'Florida' },
    { value: 'sub-24', text: 'Georgia' },
    { value: 'sub-25', text: 'Maryland' },
    { value: 'sub-26', text: 'North Carolina' },
    { value: 'sub-27', text: 'South Carolina' },
    { value: 'sub-28', text: 'Virginia' },
    { value: 'sub-29', text: 'District of Columbia' },
    { value: 'sub-30', text: 'West Virginia' },
  ],
  'div-6': [
    { value: 'sub-31', text: 'Alabama' },
    { value: 'sub-32', text: 'Kentucky' },
    { value: 'sub-33', text: 'Mississippi' },
    { value: 'sub-34', text: 'Tennessee' },
  ],
  'div-7': [
    { value: 'sub-35', text: 'Arkansas' },
    { value: 'sub-36', text: 'Louisiana' },
    { value: 'sub-37', text: 'Oklahoma' },
    { value: 'sub-38', text: 'Texas' },
  ],
  'div-8': [
    { value: 'sub-39', text: 'Arizona' },
    { value: 'sub-40', text: 'Colorado' },
    { value: 'sub-41', text: 'Idaho' },
    { value: 'sub-42', text: 'Montana' },
    { value: 'sub-43', text: 'Nevada' },
    { value: 'sub-44', text: 'New Mexico' },
    { value: 'sub-45', text: 'Utah' },
    { value: 'sub-46', text: 'Wyoming' },
  ],
  'div-9': [
    { value: 'sub-47', text: 'Alaska' },
    { value: 'sub-48', text: 'California' },
    { value: 'sub-49', text: 'Hawaii' },
    { value: 'sub-50', text: 'Oregon' },
    { value: 'sub-51', text: 'Washington' },
  ],
};

const App: FC = () => {
  const [divDisabled, setDivDisabled] = useState<boolean>(true);
  const [subDisabled, setSubDisabled] = useState<boolean>(true);
  const [divData, setDivData] = useState<Array<{ value: string; text: string }>>([]);
  const [subData, setSubData] = useState<Array<{ value: string; text: string }>>([]);

  const getData = useCallback((region: string | null, division: string | null) => {
    let arr = [];

    if (division) {
      arr = subdivisions[division];
    } else if (region) {
      arr = divisions[region];
    } else {
      arr = regions;
    }

    return arr;
  }, []);

  const regChange = useCallback(
    (event: MbscSelectChangeEvent) => {
      setDivData(getData(event.value, null));
      setDivDisabled(false);
      setSubDisabled(true);
    },
    [getData],
  );

  const divChange = useCallback(
    (event: MbscSelectChangeEvent) => {
      const val = event.value;
      if (val) {
        setSubData(getData(null, event.value));
        setSubDisabled(false);
      } else {
        setSubData([]);
        setSubDisabled(true);
      }
    },
    [getData],
  );

  return (
    <div className="mbsc-grid mbsc-grid-fixed mbsc-no-padding">
      <div className="mbsc-row">
        <div className="mbsc-col-sm-12">
          <div className="mbsc-form-group-inset">
            <Select data={getData(null, null)} touchUi={false} label="Region" placeholder="Please select..." onChange={regChange} />
            <Select
              data={divData}
              touchUi={false}
              disabled={divDisabled}
              label="Division"
              placeholder="Please select..."
              onChange={divChange}
            />
            <Select data={subData} touchUi={false} disabled={subDisabled} label="Subivision" placeholder="Please select..." />
          </div>
        </div>
      </div>
    </div>
  );
};
export default App;
```

## Implementation notes

- Three independent `Select` instances (Region, Division, Subdivision) are linked purely through app-level state - the component has no built-in hierarchy/parent-child option; each level's `data` array and `disabled` flag are recomputed and pushed down on the parent's change event.
- Region's `data` is the static top-level list; Division's and Subdivision's `data` start as empty arrays and are only populated once their preceding level has a value.
- `disabled: true` on the Division and Subdivision selects keeps them non-interactive until a value is chosen one level up; the demo's `getData(region, division)` helper looks up `divisions[region]` or `subdivisions[division]` from plain lookup objects keyed by the parent's `value`.
- The `onChange` event is the wiring mechanism: Region's change handler reads the new `value`, looks up that region's divisions, replaces Division's `data`, and clears `disabled` on Division (while re-disabling Subdivision and clearing its `data`, since the previous Division selection is no longer valid). Division's change handler does the same one level down for Subdivision; an empty/`null` `value` (cleared selection) re-disables the next level instead of populating it.
- Event/data binding differs per framework: React passes `data`/`disabled`/`onChange` as props, with `onChange` receiving `(event: MbscSelectChangeEvent)` and `event.value` holding the newly selected value; Angular binds `[data]`/`[disabled]` inputs and `(onChange)="handler($event)"` output on `<mbsc-select>`; the value is read the same way off the emitted event object in both.
- `touchUi={false}` forces the picker to render in the desktop dropdown style regardless of the input device - unrelated to the linking logic itself.
- This pattern generalizes to any number of dependent levels (e.g. maker → model → trim) by chaining the same disable-until-parent-has-value / repopulate-on-parent-change logic at each level.

## Run it in your project

1. If not signed up yet, or don't own a license, you can [start a free trial](https://mobiscroll.com/starttrial).

2. Clone the repository:

   ```
   git clone https://github.com/acidb/mobiscroll-demos-react.git
   ```

3. If not installed yet, install the Mobiscroll CLI from NPM:

   ```
   npm install -g @mobiscroll/cli
   ```

4. Config Mobiscroll - open a terminal window in the root directory and run:

   ```
   mobiscroll config react
   ```

5. Install dependencies - open a terminal window in the root directory and run:

   ```
   npm install
   ```

6. Run the project:

   ```
   npm run dev
   ```

On a trial, add `--trial` to the config command: `mobiscroll config react --trial`.

Running a demo locally requires a Mobiscroll trial or a license; setup is per framework at https://mobiscroll.com/docs/react/getting-started/installation

## Related demos

- Previous: [Filtering](https://demo.mobiscroll.com/react/select/filtering-values.md) - React select with filtering. Type in the header of the dropdown and filter values locally or remotely.
- Next: [Setting values](https://demo.mobiscroll.com/react/select/setting-values-defaults.md) - React Select scroller demo with examples for programmatically setting values and defaults for single/multiple select.
