Skip to content

Commit cbdf35b

Browse files
committed
feat: Validate children of UIBox components
1 parent 2cde9eb commit cbdf35b

File tree

5 files changed

+48
-5
lines changed

5 files changed

+48
-5
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ Accepted props:
274274
loading
275275
- `chatboxRef` (resp. `inboxRef`, `popupRef`) - Pass a ref (created with `useRef`) and it'll be set to the vanilla JS [Chatbox](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Chatbox/) (resp. [Inbox](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Inbox/), [Popup](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Popup/)) instance. See [above](#using-refs) for an example.
276276
- All [Talk.ChatboxOptions](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Session/#ChatboxOptions)
277-
- `children?: ReactNode` - Optional. You can provide an `<HtmlPanel>` component as a child to use [HTML Panels](https://talkjs.com/docs/Features/Customizations/HTML_Panels/).
277+
- `children?: ReactNode` - Optional. If provided, only [`<HtmlPanel>`](https://talkjs.com/docs/Features/Customizations/HTML_Panels/) components are allowed as children.
278278

279279
Accepted events (props that start with "on"):
280280

lib/ui/Chatbox.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
import { CSSProperties, ReactNode } from "react";
1+
import React, { CSSProperties, ReactNode } from "react";
22
import type Talk from "talkjs";
33
import { useSession } from "../SessionContext";
4-
import { getKeyForObject, splitObjectByPrefix } from "../util";
4+
import {
5+
validateChildrenAreHtmlPanels,
6+
getKeyForObject,
7+
splitObjectByPrefix,
8+
} from "../util";
59
import { useSetter, useConversation, useUIBox } from "../hooks";
610
import { FirstParameter, UIBoxProps } from "../types";
711
import { MountedBox } from "../MountedBox";
@@ -19,6 +23,12 @@ type ChatboxProps = UIBoxProps<Talk.Chatbox> &
1923
export function Chatbox(props: ChatboxProps) {
2024
const session = useSession();
2125

26+
if (!validateChildrenAreHtmlPanels(props.children)) {
27+
throw new Error(
28+
"<Chatbox> may only have <HtmlPanel> components as direct children.",
29+
);
30+
}
31+
2232
if (session) {
2333
const key = getKeyForObject(session);
2434
return <ActiveChatbox key={key} session={session} {...props} />;

lib/ui/Inbox.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { CSSProperties, ReactNode } from "react";
22
import type Talk from "talkjs";
33
import { useSession } from "../SessionContext";
4-
import { getKeyForObject, splitObjectByPrefix } from "../util";
4+
import {
5+
getKeyForObject,
6+
splitObjectByPrefix,
7+
validateChildrenAreHtmlPanels,
8+
} from "../util";
59
import { useSetter, useConversation, useUIBox } from "../hooks";
610
import { FirstParameter, UIBoxProps } from "../types";
711
import { MountedBox } from "../MountedBox";
@@ -13,11 +17,18 @@ type InboxProps = Partial<UIBoxProps<Talk.Inbox>> &
1317
loadingComponent?: ReactNode;
1418
style?: CSSProperties;
1519
className?: string;
20+
children?: React.ReactNode;
1621
};
1722

1823
export function Inbox(props: InboxProps) {
1924
const session = useSession();
2025

26+
if (!validateChildrenAreHtmlPanels(props.children)) {
27+
throw new Error(
28+
"<Inbox> may only have <HtmlPanel> components as direct children.",
29+
);
30+
}
31+
2132
if (session) {
2233
const key = getKeyForObject(session);
2334
return <ActiveInbox key={key} session={session} {...props} />;

lib/ui/Popup.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import Talk from "talkjs";
22
import { useSession } from "../SessionContext";
3-
import { getKeyForObject, splitObjectByPrefix } from "../util";
3+
import {
4+
getKeyForObject,
5+
splitObjectByPrefix,
6+
validateChildrenAreHtmlPanels,
7+
} from "../util";
48
import { useSetter, useConversation, useUIBox, useMountBox } from "../hooks";
59
import { EventListeners } from "../EventListeners";
610
import { UIBoxProps } from "../types";
@@ -9,11 +13,18 @@ type PopupProps = UIBoxProps<Talk.Popup> &
913
Talk.PopupOptions & {
1014
highlightedWords?: Parameters<Talk.Popup["setHighlightedWords"]>[0];
1115
popupRef?: React.MutableRefObject<Talk.Popup | undefined>;
16+
children?: React.ReactNode;
1217
};
1318

1419
export function Popup(props: PopupProps) {
1520
const session = useSession();
1621

22+
if (!validateChildrenAreHtmlPanels(props.children)) {
23+
throw new Error(
24+
"<Popup> may only have <HtmlPanel> components as direct children.",
25+
);
26+
}
27+
1728
if (session) {
1829
const key = getKeyForObject(session);
1930
return <ActivePopup key={key} session={session} {...props} />;

lib/util.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import React from "react";
2+
import { HtmlPanel } from "./HtmlPanel";
3+
14
export type Func = (...args: any) => any;
25

36
export interface Mountable {
@@ -63,3 +66,11 @@ export function splitObjectByPrefix<P extends string, T extends object>(
6366
}
6467
return [prefixed, unprefixed];
6568
}
69+
70+
export function validateChildrenAreHtmlPanels(children?: React.ReactNode) {
71+
if (!children) return true;
72+
73+
return React.Children.toArray(children).every((x) => {
74+
return typeof x === "object" && (x as any).type === HtmlPanel;
75+
});
76+
}

0 commit comments

Comments
 (0)