Recursive Tree Menu

The recursive tree menu can be used to render a tree-style navigation menu, where branches on the tree can be expanded to expose subcategories, which in turn can be expanded to expose their subcategories, and so on.

The component takes category as a prop, which should be an object of type Category. It also takes a prop called initialOpenMatchingCategories that opens the categories matching the current path when the component mounts.

RecursiveTree uses the function as a child pattern, and exposes arguments that can be used to construct any desired UI, rather than defining a UI itself.

RecursiveTree render function interface

Copy
Copied
export interface RecursiveTreeRender {
  /** Execute to fetch the next 3 levels of subcategories */
  fetchMore(): void;
  /** Props passed through from MenuContainer UI component */
  menuContainerProps: MenuContainerRender;
  /** An array of subcategories at the current level */
  subcategories: Category[];
  /** The current category being rendered */
  category: Category;
  /** The current category level */
  level: number;
  /** Execute to recursively render subcategories in place */
  renderSubnav: React.ReactNode[];
}

Implementation

Following is an example implementation. Note that the actual UI here could be anything. CategoryName is just a styled component that uses the level, subcategories.length, and isActiveCategory for stylistic purposes (it could indent based on level, show a + based on if there are subcategories, and switch to a - when isActive is true.)

renderSubnav is where the magic happens — place this where the menu should recursively render itself. Everything inside the RecursiveTree component will be rendered in place of renderSubnav until there are no remaining subcategories to render.

Copy
Copied
<RecursiveTree category={category}>
  {({
    menuContainerProps,
    fetchMore,
    subcategories,
    renderSubnav,
    level,
    category,
  }) => (
    <>
      <div
        onClick={() => menuContainerProps.toggleActiveCategory(category)}
        onHover={fetchMore}
      >
        <CategoryName
          level={level}
          hasSubcategories={subcategories.length > 0}
          isActive={menuContainerProps.isActiveCategory(category)}
        >
          {category.name}
        </CategoryName>
      </div>
      {renderSubnav}
    </>
  )}
</RecursiveTree>

Fetching subcategories

It is advised when using this component to only fetch 1 level of subcategories for the category that you are passing to it. This minimises the initial amount of data needed to render the menu. If you have many deeply nested categories, it is unlikely to be necessary to fetch everything at once, since not all users will view all categories.

Here is an example category/subcategory query:

Copy
Copied
import { categoryDetailFragment } from "@jetshop/core/data/fragments";
import { categoriesQuery } from "@jetshop/core/data/queries";
const CATEGORIES = gql`
  fragment categoriesWithSubcategories on Category {
    ${categoryDetailFragment}
    subcategories {
      ${categoryDetailFragment}
    }
  }
`;
const CATEGORIES_QUERY = categoriesQuery("HomeCategories", CATEGORIES);

Note that this only fetches a single subcategory. When you execute the query, make sure you only fetch 1 level of categories by passing levels: 1 in the variables object

Copy
Copied
<Query variables={{ levels: 1 }} query={CATEGORIES_QUERY}>

Eagerly fetch more subcategories

You may have noticed the fetchMore call in the initial implementation above. When executed, this will fetch the next 3 levels of subcategories under the currently-rendered category. Note, this will only execute once for any single category, so don't worry about repeated network requests

In the example, this is used on the hover event handler of each category. This means that when the user hovers over the category (thus showing some intent to open that category), its subcategories will be asynchronously fetched.

Automatically open categories matching current path

In some cases you want categories that match the current path to be automatically opened when the RecursiveTree renders. To enable this behaviour, set the prop initialOpenMatchinCategories to true. This will open the categories that have a matching path, as well as automatically fetch their subcategories to work recursively down the tree.

Copyright © Norce 2023. All right reserved.