react-email
Version:
A live preview of your emails right in your browser.
59 lines (49 loc) • 2.1 kB
text/typescript
import React from 'react';
import { isComponent } from './is-component.js';
/**
* A function made for deep mapping a React tree from a node, even through its components.
* For all the components it finds, it renders them by directly calling them. This has a few issues
* with hooks, and the only solution is `renderAsync` here, which will probably be done in the future.
*
* @param process - The callback that will be called every time a new element has been reached.
*
* For components, this is going to be called, most of the time, two times. This is because the best
* approach is to process *both* before rendering the components (i.e. on the props.children of a component element)
* and after rendering them because the children themselves might have been modified in the component's
* rendering.
*/
export function mapReactTree(
value: React.ReactNode,
process: (node: React.ReactNode) => React.ReactNode,
): React.ReactNode {
const mapped = React.Children.map(value, (node) => {
if (React.isValidElement<{ children?: React.ReactNode }>(node)) {
const newProps = { ...node.props };
if (node.props.children && !isComponent(node)) {
newProps.children = mapReactTree(node.props.children, process);
}
const processed = process(
React.cloneElement(node, newProps, newProps.children),
);
if (
React.isValidElement<{ children?: React.ReactNode }>(processed) &&
isComponent(processed)
) {
const OriginalComponent =
typeof processed.type === 'object'
? // @ts-expect-error - we know this is a component with a render function
(processed.type.render as React.FC)
: (processed.type as React.FC);
const rendered = OriginalComponent(processed.props);
const mappedRenderedNode = mapReactTree(
rendered as React.ReactNode,
process,
);
return mappedRenderedNode;
}
return processed;
}
return process(node);
});
return mapped && mapped.length === 1 ? mapped[0] : mapped;
}