I've recently had a problem with needing to traverse and transform some DOM children from a Parent Preact component:
import {isElevated} from "../stores/UserStateStore.ts";
import type {elevatedUserList} from "../../../plugins/remark/util/remarkOfWikiLinks-utils.ts";
import {Children} from "preact/compat";
interface RedactionProps {
level: number,
children: any;
}
function redactContent(secinfo: elevatedUserList, level: number, content: any): any {
return Children.map(content, (child: any): any => {
if (typeof child === 'string') {
return isElevated(secinfo, level, true) ? child : child.replace(/\S/g, '█');
} else if (child.props && child.props.children) {
const {children, ...rest} = child.props;
return {
...rest,
props: {
...child.props,
children: redactContent(secinfo, level, child.props.children)
}
};
}
return child;
});
}
export function Redaction(props: RedactionProps) {
// find metadata element from document root with id "secinfo"
const secinfo = document.getElementById('secinfo');
const elevatedUsers = JSON.parse(secinfo?.getAttribute('data-elevatedUsers') || '{}')
return (
<div class="redaction" data-level={props.level}>
{redactContent(elevatedUsers, props.level, props.children)}
</div>
)
}
The intention of the Component is to determine if it's children should be redacted or not based on some user credentials.
What I have here does the trick of traversing the children passed as props, though it also renders the original content below it.
I can't quite seem to find the issue with my code. Also, is there any form that's more maintainable or standard than my approach?
The issue is the second branch of your conditional: ...rest is not what you want, as it's child.props excluding children, you're removing all the top-level properties by accident. Spreads are awful for perf though and there's no reason to be creating a new child object anyways: just mutate .props.children and return the original child object.
I'd also suggest using toChildArray from core rather than Children from preact/compat:
import { toChildArray } from 'preact';
function redactContent(secinfo: elevatedUserList, level: number, content: any): any {
return toChildArray(content).map(child: any): any => {
if (typeof child === 'string') {
return isElevated(secinfo, level, true) ? child : child.replace(/\S/g, '█');
}
if (child.props && child.props.children) {
child.props.children = redactContent(secinfo, level, child.props.children);
return child;
}
return child;
});
}