A simple, data-driven, light-weight React Tree Menu component

Overview

React Simple Tree Menu

npm version CircleCI Storybook

Inspired by Downshift, a simple, data-driven, light-weight React Tree Menu component that:

  • does not depend on any UI framework
  • fully customizable with render props and control props
  • allows search
  • supports keyboard browsing

Check Storybook Demo.

Usage

Install with the following command in your React app:

npm i react-simple-tree-menu
// or
yarn add react-simple-tree-menu

To generate a TreeMenu, you need to provide data in the following structure.

// as an array
const treeData = [
  {
    key: 'first-level-node-1',
    label: 'Node 1 at the first level',
    ..., // any other props you need, e.g. url
    nodes: [
      {
        key: 'second-level-node-1',
        label: 'Node 1 at the second level',
        nodes: [
          {
            key: 'third-level-node-1',
            label: 'Last node of the branch',
            nodes: [] // you can remove the nodes property or leave it as an empty array
          },
        ],
      },
    ],
  },
  {
    key: 'first-level-node-2',
    label: 'Node 2 at the first level',
  },
];
// or as an object
const treeData = {
  'first-level-node-1': {               // key
    label: 'Node 1 at the first level',
    index: 0, // decide the rendering order on the same level
    ...,      // any other props you need, e.g. url
    nodes: {
      'second-level-node-1': {
        label: 'Node 1 at the second level',
        index: 0,
        nodes: {
          'third-level-node-1': {
            label: 'Node 1 at the third level',
            index: 0,
            nodes: {} // you can remove the nodes property or leave it as an empty array
          },
        },
      },
    },
  },
  'first-level-node-2': {
    label: 'Node 2 at the first level',
    index: 1,
  },
};

And then import TreeMenu and use it. By default you only need to provide data. You can have more control over the behaviors of the components using the provided API.

{items.map(props => ( // You might need to wrap the third-party component to consume the props // check the story as an example // https://github.com/iannbing/react-simple-tree-menu/blob/master/stories/index.stories.js ))} )} ">
import TreeMenu from 'react-simple-tree-menu';
...
// import default minimal styling or your own styling
import '../node_modules/react-simple-tree-menu/dist/main.css';
// Use the default minimal UI
<TreeMenu data={treeData} />

// Use any third-party UI framework
<TreeMenu
  data={treeData}
  onClickItem={({ key, label, ...props }) => {
    this.navigate(props.url); // user defined prop
  }}
  initialActiveKey='first-level-node-1/second-level-node-1' // the path to the active node
  debounceTime={125}>
    {({ search, items }) => (
        <>
          <Input onChange={e => search(e.target.value)} placeholder="Type and search" />
          <ListGroup>
            {items.map(props => (
              // You might need to wrap the third-party component to consume the props
              // check the story as an example
              // https://github.com/iannbing/react-simple-tree-menu/blob/master/stories/index.stories.js
              <ListItem {...props} />
            ))}
          </ListGroup>
        </>
    )}
</TreeMenu>

If you want to extend the minial UI components, they are exported at your disposal.

// you can import and extend the default minial UI
import TreeMenu, { defaultChildren, ItemComponent } from 'react-simple-tree-menu';

// add custom styling to the list item
<TreeMenu data={treeData}>
    {({ search, items }) => (
        <ul>
            {items.map(({key, ...props}) => (
              <ItemComponent key={key} {...props} />
            ))}
        </ul>
    )}
</TreeMenu>

// add a button to do resetOpenNodes
<TreeMenu data={treeData}>
    {({ search, items, resetOpenNodes }) => (
      <div>
        <button onClick={resetOpenNodes} />
        {defaultChildren({search, items})}
      </div>
    )}
</TreeMenu>

Keyboard browsing

When the tree menu is focused, you can use your keyboard to browse the tree.

  • UP: move the focus onto the previous node
  • DOWN: move the focus onto the next node
  • LEFT: close the current node if it has children and it is open; otherwise move the focus to the parent node
  • RIGHT: open the current node if it has children
  • ENTER: fire onClick function and set activeKey to current node

Note the difference between the state active and focused. ENTER is equivalent to the onClick event, but focus does not fire onClick.

API

TreeMenu

props description type default
data Data that defines the structure of the tree. You can nest it as many levels as you want, but note that it might cause performance issue. {[string]:TreeNode} | TreeNodeInArray[] -
activeKey the node matching this key will be active. Note that you need to provide the complete path (e.g. node-level-1/node-level-2/target-node). string ''
focusKey the node matching this key will be focused. Note that you need to provide the complete path (e.g. node-level-1/node-level-2/target-node) string ''
initialActiveKey set initial state of activeKey. Note that you need to provide the complete path (e.g. node-level-1/node-level-2/target-node). string -
initialFocusKey set initial state of focusKey. Note that you need to provide the complete path (e.g. node-level-1/node-level-2/target-node). string -
onClickItem A callback function that defines the behavior when user clicks on an node (Item): void console.warn
debounceTime debounce time for searching number 125
openNodes you can pass an array of node names to control the open state of certain branches string[] -
initialOpenNodes you can pass an array of node names to set some branches open as initial state string[] -
locale you can provide a function that converts label into string ({label, ...other}) => string ({label}) => label
hasSearch Set to false then children will not have the prop search boolean true
cacheSearch Enable/Disable cache on search boolean true
matchSearch you can define your own search function ({label, searchTerm, ...other}) => boolean ({label, searchTerm}) => isVisible
disableKeyboard Disable keyboard navigation boolean false
children a render props that provdes two props: search, items and resetOpenNodes (ChildrenProps) => React.ReactNode -

TreeNode

props description type default
label the rendered text of a Node string ''
index a number that defines the rendering order of this node on the same level; this is not needed if data is TreeNode[] number -
nodes a node without this property means that it is the last child of its branch {[string]:TreeNode} | TreeNode[] -
...other User defined props any -

TreeNodeInArray

props description type default
key Node name string -
label the rendered text of a Node string ''
nodes a node without this property means that it is the last child of its branch {[string]:TreeNode} | TreeNode[] -
...other User defined props any -

Item

props description type default
hasNodes if a TreeNode is the last node of its branch boolean false
isOpen if it is showing its children boolean false
level the level of the current node (root is zero) number 0
key key of a TreeNode string -
label TreeNode label string -
...other User defined props any -

ChildrenProps

props description type default
search A function that takes a string to filter the label of the item (only available if hasSearch is true) (value: string) => void -
searchTerm the search term that is currently applied (only available if hasSearch is true) string -
items An array of TreeMenuItem TreeMenuItem[] []
resetOpenNodes A function that resets the openNodes, by default it will close all nodes. activeKey is an optional parameter that will highlight the node at the given path. focusKey is also an optional parameter that will set the focus (for keyboard control) to the given path. Both activeKey/focusKey must be provided with the complete path (e.g. node-level-1/node-level-2/target-node). activeKey will not highlight any nodes if not provided. focusKey will default to activeKey if not provided. (openNodes: string[], activeKey?: string, focusKey?: string) => void [],'',''

TreeMenuItem

props description type default
hasNodes if a TreeNode is the last node of its branch boolean false
isOpen if it is showing its children boolean false
openNodes an array of all the open node names string[] -
level the level of the current node (root is zero) number 0
key key of a TreeNode string -
parent key of the parent node string -
searchTerm user provided search term string -
label TreeNode label string -
active if current node is being selected boolean -
focused if current node is being focused boolean -
onClick a callback function that is run when the node is clicked Function -
toggleNode a function that toggles the node (only availavble if it has children) Function -
...other User defined props {[string]: any} -
Comments
  • controlling the treemenu both from parent component as well as the +/- against node

    controlling the treemenu both from parent component as well as the +/- against node

    Hi - Thanks for this useful component. I was wondering if there is a way to control the tree menu via both the parent component (as demonstrated in the story (https://iannbing.github.io/react-simple-tree-menu/?path=/story/treemenu--control-treemenu-from-its-parent) as well as via the +/- next to each menu item? Looking at the code it seems if you use openNodes instead of initialOpenNodes it stops you from controlling the tree menu using the +/-. Any particular reason why thats the case or suggestions on how I can get both of them to work?

    My particular use case is where i have a list of items and on clicking on each item it changes a view displaying additional details depending on which item is selected. The view is where i have the tree menu component. So the list of items is my parent component and view is child. And based on which item is selected and according the value of a particular field of the item i set the active key on the tree menu and the open nodes. But I also want to be able to change the value to something else and hence it would require me to toggle the +/- to select any other menu from the tree.

    IMG_20191101_104027__01

    opened by dghosh 7
  • "Warning: u: `key` is not a prop" in console.

    Great tree view component, very powerful and easy to use. Thank you for releasing it!

    I just implemented it and followed the data structure as per the documentation. In my console react is throwing an error:

    Warning: u: key is not a prop. Trying to access it will result in undefined being returned. If you need to access the same value within the child component, you should pass it as a different prop.

    Is this something that I can fix or will it require a update to the component code?

    Thank you!

    opened by JasonLunsford 7
  • initialOpenNodes is not working as expected

    initialOpenNodes is not working as expected

    Hey,

    I stumbled upon issue #36 and tried to use your solution for my issue. Unfortuantely, initialOpenNodes is not working for me when using version 1.1.2. I am able to control the open nodes using the property openNodes instead of initialOpenNodes, but the user is not able to interact with the tree in this case. I want the user to be able to interact with the tree and to control the open/collapsed items from my code.

    Below you can find the code for my tree menu component.

    class GraphPropertyView extends React.Component {
      handleClick = (key, label, ...properties) => {};
    
      render() {
        const { classes } = this.props;
    
        const properties = this.processProperties(this.props.properties, {
          index: 0,
        });
        const openNodeKeys = this.determineOpenNodes(properties, {
          currentLevel: 0,
        });
        console.log(openNodeKeys);
        return (
          <div className={classes.graphPropertyContainer}>
            <TreeMenu
              data={properties}
              initialOpenNodes={openNodeKeys}
              onClickItem={this.handleClick}
              hasSearch={false}
            />
          </div>
        );
      }
    
      // emulate call by reference by using a context object
      processProperties(properties, context) {
        let treeData = [];
        for (let property of properties) {
          let addedProperty = {
            key: context.index.toString(),
            label: property.name,
            nodes: []
          };
          ++context.index;
          if (typeof property === "object" && property.children) {
            addedProperty.nodes = this.processProperties(
              property.children,
              context
            );
          }
          treeData.push(addedProperty);
        }
        return treeData;
      }
    
      // emulate call by reference by using a context object
      determineOpenNodes(properties, context) {
        let openNodeKeys = [];
        for (let property of properties) {
          if (this.props.currentLevel > context.currentLevel) {
            openNodeKeys.push(property.key);
          }
          if ((property.nodes) && (property.nodes.length > 0)) {
            ++context.currentLevel;
            openNodeKeys.push(...this.determineOpenNodes(property.nodes, context));
            --context.currentLevel;
          }
        }
        return openNodeKeys;
      }
    }
    

    Btw, thank you so much for your great work!

    question 
    opened by agentS 7
  • Can we add custom icons/images to the tree for each node?

    Can we add custom icons/images to the tree for each node?

    Hi, thanks for such an easy to use tree view component. It helped me a lot as I'm using it to display dynamic data fetched from the back-end. I wanted to know if we can add custom icons/images to each tree node instead of the default "+/-". If possible, could you please guide me on how to implement the same as I'm new to React.

    good first issue 
    opened by Vassant 6
  • on open?

    on open?

    Hi, really like this component, however I'm not sure how to fire an event when an item is opened to reveal its children, all I can see is the onClick event. Any way to asynchronously load more children when an item is opened?

    Thanks

    question 
    opened by mhutfield 6
  • Another On Open question

    Another On Open question

    Opening a new issue as the prior question is closed.

    I would like to hook into the collapse/expand onClick event (the event that occurs when the icon is clicked, not when the node itself is clicked). In the prior question this code was given:

    <ToggleIcon on={isOpen} onClick={() => { toggleNode(); yourOwnFunction(); }} />

    This definitely helps a little bit, but what I still don't understand is where this code is meant to go? Is it possible to use this code without creating a custom "MyTreeMenu" component?

    Or, is it just possible to surface this functionality by default? Maybe call the prop onNodeToggle or something?

    Thanks (again)!

    wontfix 
    opened by JasonLunsford 5
  • is there a way to bind keyboard shortcuts for the TreeMenu

    is there a way to bind keyboard shortcuts for the TreeMenu

    I'd like to be able to browse the menu with arrow up/down, left/right keys. Up/down would go up & down in the menu items, left/right would close/open the current item if it has children.

    Would it work to hook keyboard events and then synthesize click events based on what keys were pressed?

    enhancement 
    opened by nurpax 5
  • Remove border line.

    Remove border line.

    Thanks for posting "react-simple-tree-menu". And can I have a question? I can see the blue border out of the group tree when I click the children item. I don't want that. I need to remove that border. Where I can do that?

    opened by gezhe-wu 4
  • initialOpenNodes is not updated on re-render of the component

    initialOpenNodes is not updated on re-render of the component

    I am trying to keep the first node to be open when my component renders but the data is still not available at the point (initialOpenNodes={['']}) as it is fetched via API. Once the data is fetched, TreeMenu renders again with the required data (initialOpenNodes={['<node_name'>']}) but it is not taking the updated prop. I tested this by hardcoding the value and it works fine. So, I assume that the value set on first render is what TreeMenu consumes ignoring the subsequent renders.

    Can you please look into this?

    opened by che7ven 4
  • How can I put a custom icon to open/close item?

    How can I put a custom icon to open/close item?

    I can see in the storybook an example where you use a custom icon when the item is open or close, but in the documentation I can't see how to use a custom icon to open/close item.

    opened by lcuriel 4
  • [Feature] Initial Values should be effective in every render

    [Feature] Initial Values should be effective in every render

    The following values have no effect on the initially rendered tree menu...

    initialOpenNodes initialActiveKey initialFocusKey

    Also, the demo at https://codesandbox.io/s/react-tree-menu-demo-dbx8d is not working with the same issue.

    good first issue 
    opened by pmh1031 4
  • How to add new nodes in inner levels?

    How to add new nodes in inner levels?

    I'm using ItemComponent in ul and want to add new nodes to different levels in the tree using input.Is there any way to accomplish this without inserting level?

    opened by Maduz0097 0
  • On Click listener for expand/collapse button

    On Click listener for expand/collapse button

    Problem - Basically I want to control expand/collapse from both UI and outside UI. On setting openNodes params, I am able to expand/collapse from outside UI but I am not able to expand/collapse through UI.

    Possible solution - If we have 'on click listener' for expand/collapse button, we will be able to maintain the state of openNodes params through useState and we will be able to set the openNodes state from 'on click listener' and outside as well.

    Another possible solution would be to use useEffect to enforce the re-rendering the component from outside by using initialOpenNodes params but I am losing the previous openNodes state in that case.

    In case, there is any workaround to achieve this in current version, please enlighten me.

    opened by impsinha 0
  • How to implement Prev & Next buttons to traverse menu tree

    How to implement Prev & Next buttons to traverse menu tree

    Thanks for such a nice tool. we are able to do lot of things like navigating content through menu traversing. we have requirement to implement Next & Previous actions to traverse menu , please suggest .

    opened by maheshtalada 0
  • `TreeMenuItem` type is just for `li` elements

    `TreeMenuItem` type is just for `li` elements

    It should probably be for any HTML element.

    https://github.com/iannbing/react-simple-tree-menu/blob/2d9990ab1b51c01dec701d8dd35f47bd5cd160af/src/TreeMenu/renderProps.tsx#L25

    opened by corysimmons 0
Releases(v1.1.18)
Owner
Huang-Ming Chang
Huang-Ming Chang
Smart data-driven menu rendered in an overlay

React Data Menu Smart data-driven menu rendered in an overlay. Hints-based aligning with custom renderers and factories. Never clipped by other compon

Danko Kozar 106 Sep 11, 2022
React component for building accessible menu, dropdown, submenu, context menu and more.

React-Menu An accessible and keyboard-friendly React menu library. Live examples and docs Features React menu components for easy and fast web develop

Zheng Song 837 Jan 9, 2023
Simple Context menu component for react showing all parent's node menus in theirs contexts.

Simple Context menu component for react showing all inherited parents menu with SSR compatibility.

Nathan Braun 6 Nov 13, 2022
A simple sliding side menu component for React

Cheeseburger Menu A simple sliding side menu component for React. This component provides the sliding menu only, not the hamburger button. For your bu

Eddie McLean 19 May 27, 2022
A react component that displays an unlimited deep menu

react-infinity-menu An unlimited deep side menu Live Demo Demo Installation npm install react-infinity-menu How to use import InfinityMenu from "react

Social Tables 58 Dec 5, 2022
Animated hamburger menu icons for React (1.5 KB) 🍔

‌ ‌ Animated hamburger menu icons for React Hamburger menu icons for React, with CSS-driven transitions. Created to be as elegant and performant as po

Luuk de Vlieger 709 Dec 31, 2022
React Dropdown Menu

React Dropdown Menu

Mikkel Laursen 142 Dec 3, 2022
Hamburger Menu React JS Using Third Party Package ReactJS - Popup

In this project, let's build a Hamburger Menu app by applying the concepts we have learned till now. Refer to the image below: Design Files Click to v

null 2 Dec 6, 2021
React dropdown menu components

react-menu-list This project is a set of components for building menus. This project works well for dropdown and autocomplete menus. The menus are cor

Streak 79 Nov 16, 2022
Add a context menu to your react app with ease

Documentation Go here. Installation Using yarn $ yarn add react-contexify Using npm $ npm install --save react-contexify The gist import React from 'r

Fadi Khadra 868 Jan 8, 2023
Radial Menu for FiveM,built with React

BCS Radial Menu This project is to freshen up the options for free radial menu f

BagusCodeStudio 13 Nov 7, 2022
🍪 A stylized command menu for React.

?? Superkey is under development and is not ready for production. If you have any bugs or problems please create an issue. ?? Website (working ?? ) •

Pablo Hdez 57 Dec 31, 2022
📱 A performant, easy to use hold to open context menu for React Native powered by Reanimated 🚀

React Native Hold Menu A performant, easy to use hold to open context menu for React Native powered by Reanimated. ?? This package is experimental and

Enes 1k Jan 6, 2023
Simple lightweight (<2kb) animated slider component.

react-slide-out Simple lightweight (<2kb) animated slider component. Usability import Slider from 'react-slide-out'; and include css file import 'reac

Gogo 14 Aug 22, 2020
A simple Hook for creating fully accessible dropdown menus in React

This Hook handles all the accessibility logic when building a dropdown menu, dropdown button, etc., and leaves the design completely up to you. It also handles the logic for closing the menu when you click outside of it.

Sparksuite 111 Dec 22, 2022
:hamburger: An off-canvas sidebar component with a collection of effects and styles using CSS transitions and SVG path animations

react-burger-menu An off-canvas sidebar React component with a collection of effects and styles using CSS transitions and SVG path animations. Using R

Imogen 4.8k Jan 8, 2023
:art: Off-canvas menus for React.

React Off-Canvas Off-canvas menus for React. Installation $ npm install --save react-offcanvas Usage Basic Usage <OffCanvas width={300} transition

Vu Tran 38 Nov 10, 2022
A react lib for building circular menus in a very easy and handy way.

react-planet A react lib for building circular menus in a very easy and handy way. Live-Demo: STORYBOOK Read the full story @ Medium or innFactory-Blo

innFactory 140 Nov 16, 2022
React dismissable context and hook with layers (nesting) support

Context and hook to add support for nested, auto-dismissable layers. State can be globally controlled through context. Best used with react-popper.

Voiceflow 13 Nov 15, 2022