Issue
I have recently moved over to typescript and have been enjoying the benefits it has provided. I was trying to move over a pattern I used in React with javascript that I liked 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 am stumped on 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. Any typescript masters out there who know how I might fix this?
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} />
)
}
Edit: This is the error message
TS18049: props is possibly null or undefined
Solution
Why doesn't it work? Because that's not how TypeScript works.
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'.
Solution 1 - Children Structure
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.
1. We need to adjust the 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:
- Accept
definedValue
string | undefined (not the boolean state) - Re-define the
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.
2. We need to adjust 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.
Answered By - Michael Harley
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.