I am trying to move over to TypeScript a pattern I used in React with JavaScript of a RenderIf
component that checked whether it should show the content below or return null
. The following code does work in TypeScript when not passing a potentially undefined value. I don't know how to help the TypeScript compiler realize that we already did the &&
check to validate that the value exists if the value can be undefined.
RenderIf
component:
type RenderIfProps = {
condition: boolean;
};
export const RenderIf: React.FC<RenderIfProps> = (props) => {
const { condition = false, children } = props;
const isConditionMetAndHasChildren = condition === true && !!children;
if (isConditionMetAndHasChildren) {
return <>{children}</>;
}
return null;
};
Example of TypeScript not being happy with RenderIf
:
const someComponent = (props: { someValue?: string }) => {
return (
<RenderIf condition={!!props?.someValue}>
<ComponentA definedValue={props.someValue} />
</RenderIf>
)
}
Example of TypeScript being happy with &&
:
const someComponent = (props: { someValue?: string }) => {
return (
!!props?.someValue && <ComponentA definedValue={props.someValue} />
)
}
This is the error message:
TS18049: props is possibly null or undefined
How might I fix this?
The props.someValue
typing will always be the same on the same scope. In this case, the RenderIf
and ComponentA
functional components are in the same scope.
function SomeComponent(props: { someValue?: string }) {
return (
<RenderIf condition={!!props?.someValue}>
<ComponentA definedValue={props.someValue} />
</RenderIf>
);
}
That's why definedValue
props on ComponentA
get this error: Type 'string | undefined' is not assignable to type 'string'.
To make it work, we need to define the typescript scope of the functional component. If you want to keep the children-like structure like your example, I can share a solution to this.
RenderIf
component:type RenderIfProps = {
definedValue?: string;
children(definedValue: string): ReactElement;
};
function RenderIf(props: PropsWithChildren<RenderIfProps>) {
const { definedValue, children } = props;
if (!definedValue || !children) {
return null;
}
return children(definedValue);
}
I adjusted the RenderIf
functional component to:
definedValue
string | undefined (not the boolean state)children
props from accepting a component to a function, this way I can change the scope of the typescript into the typing that I want.SomeComponent
when calling RenderIf
function SomeComponent(props: { someValue?: string }) {
return (
<RenderIf definedValue={props?.someValue}>
{(value) => <ComponentA definedValue={value} />}
</RenderIf>
);
}
I change how I call the children inside the RenderIf
component. Instead of directly adding a component, I add a function, in which the parameter is a string
type, not string | undefined
like before. This is what I previously called changing the scope of the typing.
CodeSandbox example: here
I put the most similar solution to your example. Depending on the use case, this solution might not be the best. And since there are a lot of ways to achieve what you want, I put the "Solution 1" above.
Let me know if it doesn't work for you.