'Can discriminated union applied for interface or is there an alternative way?
I have integrated context api to Layout component and is facing type problem. Layout component takes 2 sub-components which are Layout.BodyContent and Layout.RightPanel. Context passes rightPanelOpen prop to Layout. When rightPanelOpen is true, both BodyContent and RightPanel need to be displayed. This works. When rightPanelOpen is false then only RightPanel needs to be displayed. This is where the problem is. When rightPanelOpen is false and user supplies both BodyContent and RightPanel then only BodyContent is displayed. This part is working fine, but if user does not supply RightPanel then I get type error stating only one child was provided. This make sense since my interface contains two children in an array children: [ReactElement<BodyContentProps>, ReactElement<RightPanelProps>]; Before migrating context api, I was able to solve this issue by using discriminated union with type like this:
export type LayoutProps =
| {
rightPanelOpen?: true;
children: [ReactElement<BodyContentProps>, ReactElement<RightPanelProps>];
}
| {
rightPanelOpen?: false;
children:
| ReactElement<BodyContentProps>
| [ReactElement<BodyContentProps>, ReactElement<RightPanelProps>];
};
But now rightPanelOpen is coming from context as interface and I don't know how to turn that discriminated union. Is there an approach that can solve this situation? https://codesandbox.io/s/layout-context-ts-jdin7i?file=/src/App.tsx:282-634
App.tsx
import "./styles.css";
import React, { cloneElement, ReactElement } from "react";
import { RightPanel, RightPanelProps } from "./RightPanel";
import { BodyContent, BodyContentProps } from "./BodyContent";
import { LayoutContextInterface, LayoutContext } from "./LayoutContext";
// export type LayoutProps =
// | {
// rightPanelOpen?: true;
// children: [ReactElement<BodyContentProps>, ReactElement<RightPanelProps>];
// }
// | {
// rightPanelOpen?: false;
// children:
// | ReactElement<BodyContentProps>
// | [ReactElement<BodyContentProps>, ReactElement<RightPanelProps>];
// };
export interface LayoutInterface extends LayoutContextInterface {
children: [ReactElement<BodyContentProps>, ReactElement<RightPanelProps>];
}
export const Layout: React.FunctionComponent<LayoutInterface> & {
RightPanel: typeof RightPanel;
BodyContent: typeof BodyContent;
} = function Layout({ rightPanelOpen, children }: LayoutInterface) {
const context = React.useMemo(
() => ({
rightPanelOpen
}),
[rightPanelOpen]
);
let content;
if (rightPanelOpen) {
const [bodyContent, rightPanel] = children;
content = (
<>
{/* {cloneElement(bodyContent, { sectioned: true })} */}
{bodyContent}
{rightPanel}
</>
);
} else {
if (Array.isArray(children)) {
content = <>{children[0]}</>;
} else {
content = <>{children}</>;
}
}
return (
<LayoutContext.Provider value={context}>
<div className="layout-flex">{content}</div>
</LayoutContext.Provider>
);
};
export default function App() {
return (
<Layout rightPanelOpen={false}>
<Layout.BodyContent>
<h1> This is for </h1>
<h2> Body content </h2>
</Layout.BodyContent>
{/* <Layout.RightPanel>
<h1>Right Side Panel</h1>
</Layout.RightPanel> */}
</Layout>
);
}
Layout.RightPanel = RightPanel;
Layout.BodyContent = BodyContent;
BodyContent.tsx
import "./styles.css";
import React from "react";
import { LayoutContextInterface, LayoutContext } from "./LayoutContext";
export interface BodyContentProps {
children?: React.ReactNode;
}
export function BodyContent({ children }: BodyContentProps) {
const context = React.useContext(LayoutContext);
return (
<div className={context.rightPanelOpen ? "bodyWidth-70" : "bodyWidth-100"}>
{children}
</div>
);
}
RightPanel.tsx
import "./styles.css";
export interface RightPanelProps {
children?: React.ReactNode;
}
export function RightPanel({ children }: RightPanelProps) {
return <div className="rightSideWidth">{children}</div>;
}
LayoutContext.tsx
import React from "react";
export interface LayoutContextInterface {
rightPanelOpen?: boolean;
}
const defaultLayoutContext: LayoutContextInterface = {
rightPanelOpen: false
};
export const LayoutContext = React.createContext<LayoutContextInterface>(
defaultLayoutContext
);
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
