Reputation: 2147
Is there a way to create React components (or here more specifically: a context menu) on the fly, i.e. only when the user requested such a context menu?
All the required information for constructing the individual context menu is already present on the component triggering it, meaning there is no need to wait for any async fetching of data.
In other contexts this can be easily achieved, but I can't wrap my head around how this should be done in a React+Redux application.
The only thing that comes to mind while writing this, would be to send an action when a context menu is being triggered and include this single context menu as some kind of popup dialog in the resulting re-rendering.
That would at least avoid the premature creation of heaps of invisible components. But it somehow feels like an anti-pattern. What am I missing here?
The state to be displayed in my React Application is in a hierarchical structure realistically nested up to six or seven levels deep.
On the top level it's a simple list. But each list item has a structure like this:
Item = {
priorChildren: List<Item>
content: List<NonHierarchicalData>
laterChildren: List<Item>
}
These Item
s can be nested in any way the user sees fit.
Item
should offer up to five actions via a context menu depending on its position in the hierarchy (there are probably not more than 10 permutations).NonHierarchicalData
should also offer a context menu depending on its data with potentially up to 30 menu items. Since the data contains user input which is incorporated into the menu items, each context menu is potentially unique.A realistic state might contain around 30 or 40 of these Item
s, each with 5 to 10 NonHierarchicalData
element. Based on these numbers I can end up with ca. 250 different context menus consisting of over 5000 menu items.
At any given time the user will probably only open one or two context menus before selecting an action and thereby triggering a state change and thereby a rerendering.
On one hand there are great existing libraries like react-contextmenu which expect all context menu variations to be known upfront and to be created as part of the DOM only to be made visible when required.
On the other hand it feels counter-intuitive to create hundreds of those menus with thousands of entries just to display a few dozen of them before they are inevitably re-created after the next state change.
Upvotes: 4
Views: 7821
Reputation: 46
Here an alternative that handle inheritable menus;
This lib allow menus in child components to also show the parents menus in theirs contexts, optionally basing the browser event & the parents menus.
https://github.com/n8tz/react-inheritable-context-menu
check the demo here :
Upvotes: 0
Reputation: 2147
As of its version v2.3.1
, the react-contextmenu offers a connectMenu()
helper to be able to pass a trigger's props down to a single registered menu in order to alter its rendering.
However that might be refactored in the future so use with caution in newer versions.
In short: you can have a single wrapper component for a ContextMenu
and enable it to receive it's trigger props via the aforementioned connectMenu()
helper.
const MENU_ID = 'some-identifier';
const DynamicMenu = (props) => {
const { id, trigger } = props;
return (
<ContextMenu id={id}>
{trigger && <MenuItem>{trigger.itemLabel}</MenuItem>}
</ContextMenu>
);
};
const ConnectedMenu = connectMenu(MENU_ID)(DynamicMenu);
This connected wrapper of a ContextMenu
(here ConnectedMenu
) only has to be included once in some parent component, while multiple trigger can use it for menus with differing contents. Beware that the ContextMenuTrigger
's connect
prop must forward either all of its props
or at least the part relevant to the menu being built. E.g.
const DynamicMenuExample = (props) => (
<div>
<ContextMenuTrigger id={MENU_ID} itemLabel='one'
connect={props => props}>
{'Click me for a menu with a "one" item.'}
</ContextMenuTrigger>
<ContextMenuTrigger id={MENU_ID}
connect={() => ({itemLabel: 'other'})}>
{'Click me for a menu with an "other" item.'}
</ContextMenuTrigger>
<ContextMenuTrigger id={MENU_ID} itemLabel='third'
connect={props => ({ itemLabel: props.itemLabel})}>
{'Click me for a menu with a "third" item.'}
</ContextMenuTrigger>
<ConnectedMenu />
</div>
);
Refer to the project's DynamicMenu demo for a complete example.
Upvotes: 6