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
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.
<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:
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
<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.