Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make a React "If" component that acts like a real "if" in Typescript?

I made a simple <If /> function component using React:

import React, { ReactElement } from "react";

interface Props {
    condition: boolean;
    comment?: any;
}

export function If(props: React.PropsWithChildren<Props>): ReactElement | null {
    if (props.condition) {
        return <>{props.children}</>;
    }

    return null;
}

It lets me write a cleaner code, such as:

render() {

    ...
    <If condition={truthy}>
       presnet if truthy
    </If>
    ...

In most cases, it works good, But when I want to check for example if a given variable is not defined and then pass it as property, it becomes an issue. I'll give an example:

Let's say I have a component called <Animal /> which has the following Props:

interface AnimalProps {
  animal: Animal;
}

and now I have another component which renders the following DOM:

const animal: Animal | undefined = ...;

return (
  <If condition={animal !== undefined} comment="if animal is defined, then present it">
    <Animal animal={animal} /> // <-- Error! expected Animal, but got Animal | undefined
  </If>
);

As I commented, althought in fact animal is not defined, I've got no way of telling Typescript that I already checked it. Assertion of animal! would work, but that's not what I'm looking for.

Any ideas?

like image 807
Eliya Cohen Avatar asked Feb 25 '20 11:02

Eliya Cohen


People also ask

Can I write if-else condition in JSX?

if-else statements don't work inside JSX. This is because JSX is just syntactic sugar for function calls and object construction.

How do you render two components conditionally in React?

Use a ternary operator to conditionally render multiple elements in React. When returning multiple sibling elements, make sure to wrap them in a React fragment. Copied! We used a ternary operator to conditionally render multiple elements.

Can I use typescript with react?

There are a lot of React components available on-line, and if you're using TypeScript with React, it can't sometimes be a little disappointing because many of those components don't have their type definitions.

Can I create a type definition file for a React component?

Fear not, creating a type definition file is easy, and because you have already saved some time at work using a component built by somebody else, maybe you can use that time and collaborate creating the type definition file for the component. In my case, I was working with the react-selectize component.

What is react and how does it work?

What is React? I’m glad you asked! React is a development library created by Facebook which allows developers to break their code up into bite-sized, reuseable chunks called components. Components are created separately and are then put together to create a dynamic and fully functioning application.

How to use conditional rendering in react?

When we build a react application, we may often need to display or hide some content based on a certain condition. Conditional rendering in react works the same way as the conditions work in JavaScript. First, we will create a new file called UserGreetings.Js; within the file, let’s create a class component.


2 Answers

It seems impossible.

Reason: if we change If component's content from

if (props.condition) {
  ...
}

to it's opposite

if (!props.condition) {
  ...
}

You will find out that this time you want the type diff been processed in the opposite way.

  <If condition={animal === undefined} comment="if animal is defined, then present it">
    <Animal animal={animal} /> // <-- Error! expected Animal, but got Animal | undefined
  </If>

Which means it's not independent, leading to the conclusion that it's not possible to make such type diff in this condition, without touching either of the two components.


I'm not sure what's the best approach, but here is one of my thought.

You could define Animal component's props animal with the typescript's
Distributive conditional types: NonNullable.

Document

type T34 = NonNullable<string | number | undefined>;  // string | number

Usage

interface AnimalProps {
  // Before
  animal: Animal;
  // After
  animal: NonNullable<Animal>;
}

It's not generated by the If component's condition, but since you only use the child component inside that condition, it makes some sense to design the child component's props as none nullable,under the condition that

type Animal do contain undefined.

like image 172
keikai Avatar answered Oct 11 '22 10:10

keikai


Short answer?

You can't.

Since you've defined animal as Animal | undefined, the only way to remove undefined is to either create a guard or recast animal as something else. You've hidden the type guard in your condition property, and TypeScript has no way of knowing what is happening there, so it cannot know you are choosing between Animal and undefined. You'll need to cast it or use !.

Consider, though: this may feel cleaner, but it creates a piece of code that needs to be understood and maintained, perhaps by someone else further down the line. In essence you're creating a new language, made of React components, that someone will need to learn in addition to TypeScript.

An alternative method to conditionally output JSX is to define variables in render that contain your conditional content, such as

render() {
  const conditionalComponent = condition ? <Component/> : null;

  return (
    <Zoo>
      { conditionalComponent }
    </Zoo>
  );
}

This is a standard approach other developers will instantly recognize and not have to look up.

like image 41
Michael Landis Avatar answered Oct 11 '22 11:10

Michael Landis