Skip to content

Instantly share code, notes, and snippets.

@coryhouse
Last active April 14, 2021 07:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save coryhouse/f972ade3ce0ed2215a8211487ebdb751 to your computer and use it in GitHub Desktop.
Save coryhouse/f972ade3ce0ed2215a8211487ebdb751 to your computer and use it in GitHub Desktop.
Only allow certain child element types in React component
import React, { Suspense } from "react";
import "./InlineAlert.scss";
import cx from "classnames";
import * as ReactIs from "react-is";
const allowedChildren = ["string", "span", "em", "b", "i", "strong"];
type InlineAlertProps = {
/** Message to display */
children: React.ReactNode;
};
export default function InlineAlert(props: InlineAlertProps) {
function isSupportedElement(child: React.ReactElement) {
if (ReactIs.isFragment(child)) return true;
return allowedChildren.some((c) => c === child.type);
}
// Only certain child elements are accepted. Recursively check child elements to assure all elements are supported.
function validateChildren(children: React.ReactNode): any {
return React.Children.map(children, (child) => {
if (!React.isValidElement(child)) return child;
let elementChild: React.ReactElement = child;
if (child.props.children) validateChildren(elementChild.props.children);
if (!isSupportedElement(elementChild)) {
throw new Error(
`Children of type ${
child.type
} aren't permitted. Only the following child elements are allowed in Inline Alert: ${allowedChildren.join(
", "
)}`
);
}
return elementChild;
});
}
validateChildren(props.children);
return (
<div role="alert">
{props.children}
</div>
);
}
// Associated tests:
import React from "react";
import InlineAlert from "../components/InlineAlert";
import { render } from "@testing-library/react";
describe("InlineAlert", () => {
// These settings avoid adding noise to the console when expected errors below occur.
const spiedError = jest.spyOn(console, "error");
beforeEach(() => {
spiedError.mockImplementation(() => {});
});
afterEach(() => {
spiedError.mockRestore();
});
it("should support using allowed elements", () => {
render(
<InlineAlert type="info">
<span>test</span>
</InlineAlert>
);
});
it("should throw an error when passed an unsupported root element", () => {
expect(() =>
render(
<InlineAlert type="info">
<div>This root div should fail</div>
</InlineAlert>
)
).toThrowError(
"Children of type div aren't permitted. Only the following child elements are allowed in Inline Alert: string, span, em, b, i, strong"
);
});
it("should throw an error when passed unsupported nested elements", () => {
expect(() =>
render(
<InlineAlert type="info">
<span>
<p>This nested p should throw since p tags aren't allowed</p>
</span>
</InlineAlert>
)
).toThrow(
"Children of type p aren't permitted. Only the following child elements are allowed in Inline Alert: string, span, em, b, i, strong"
);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment