RecursiveNodeRenderer Component
The RecursiveNodeRenderer
component is a core part of the @keyx/builder
library, responsible for recursively rendering a node tree. It takes a Node
object and a map of renderers
to determine how each node should be displayed. It also supports propInjectors
to dynamically add props to rendered components.
Usage
Typically, you won't directly use RecursiveNodeRenderer
in your application code. It's internally used by the Builder
component to render the rootNode
and its descendants. However, understanding its props is crucial for customizing the rendering behavior.
Props
The RecursiveNodeRenderer
component accepts the following props:
node: Node<BuilderNodeData>
: The current node to be rendered. This node contains thedata
(includingtype
) andchildren
that define its structure and content.renderers: RenderersMap
: An object where keys are nodetype
strings and values areRendererComponent
s. TheRecursiveNodeRenderer
looks up the appropriate component based onnode.data.type
.propInjectors: PropInjector[]
: An optional array ofPropInjector
components. These components wrap the rendered element and can inject additional props, allowing for dynamic modifications to the rendering process....restGlobalProps
: Any other props passed toRecursiveNodeRenderer
(e.g.,formData
,onFormDataChange
,currentPathPrefix
,arrayIndices
from theBuilder
component) are spread onto theRendererComponent
and also passed toPropInjector
components.
How it Works
- Renderer Lookup: The component first attempts to find a
RendererComponent
in therenderers
map usingnode.data.type
. - Unknown Type Handling: If no renderer is found for a given
node.data.type
, a warning is logged to the console, and a default fallbackdiv
element is rendered. This fallback includes a visual indicator (red dashed border) and displays the node's data for debugging purposes. Its children are still recursively rendered. - Children Rendering: If the current node has children,
RecursiveNodeRenderer
recursively calls itself for each child, passing down therenderers
,propInjectors
, andrestGlobalProps
. The result of these recursive calls is collected intorenderedChildren
. - Prop Injection: Before rendering the final element, the
propInjectors
are applied. EachPropInjector
component wraps the previously rendered element, allowing it to modify or add props to the component it wraps. Injectors are applied in reverse order of thepropInjectors
array, meaning the last injector in the array will be the outermost wrapper. - Final Render: The selected
RendererComponent
is rendered with thenode
,renderedChildren
, and allrestGlobalProps
.
Customizing Rendering
To customize how different node types are rendered, you provide a renderers
map to the Builder
component (which then passes it to RecursiveNodeRenderer
). Each renderer component receives node
, children
, and any restGlobalProps
.
import React from 'react';
import { Builder, Node, type BuilderNodeData, type RenderersMap } from '@keyx/builder';
// Define your custom renderers
const myRenderers: RenderersMap = {
document: ({ node, children }) => (
<div style={{ border: '1px solid blue', padding: '10px' }}>
<h1>Document Root</h1>
{children} {/* Renders children recursively */}
</div>
),
paragraph: ({ node }) => (
<p>{node.data.textContent as string}</p>
),
image: ({ node }) => (
<img src={node.data.src as string} alt={node.data.alt as string} style={{ maxWidth: '100%' }} />
),
};
// Example of a PropInjector
const MyPropInjector: PropInjector = ({ node, children, ...rest }) => {
// You can add/modify props here before passing them to children
const injectedProps = {
'data-node-id': node.id,
'data-node-type': node.data.type,
// ... other props based on node data or global props
};
// React.cloneElement is used to inject props into the child element
return React.cloneElement(children as React.ReactElement, injectedProps);
};
// Example usage with Builder
const rootNode = new Node<BuilderNodeData>({
type: "document",
children: [
{ type: "paragraph", textContent: "Hello from a paragraph!" },
{ type: "image", src: "https://example.com/image.jpg", alt: "Example Image" },
],
});
const handleNodeUpdate = (changedNode, type, details) => {
console.log("Node updated:", changedNode.id, type, details);
};
function MyCustomBuilderApp() {
return (
<Builder
rootNode={rootNode}
renderers={myRenderers}
propInjectors={[MyPropInjector]} // Pass your prop injectors here
onNodeUpdate={handleNodeUpdate}
// ... any other global props
/>
);
}