skip to Main Content

With Typescript and React is it possible the infer a type based on an extra property that I provide along with a functional component?

Here is (a simplified version of) my code:

const openModal = (element) => {
    // ...
}

// Define the components with their specific resultType:
const SomeModal: React.FC & { resultType: boolean } = () => <div />;
SomeModal.resultType = true;

const SomeOtherModal: React.FC & { resultType: string } = () => <div />;
SomeOtherModal.resultType = "string";

let someResult = openModal(<SomeModal />);
let someOtherResult = openModal(<SomeOtherModal />);

The code here is simplified but assume that there are a lot of different types of Modals and that openModal has no clue of which type of Modal comes in

Is there a way to infer that someResult wil be a boolean type and someOtherResult will be a string type based on the resultType of the element that I’m supplying to openModal?

I’ve tried a lot of options using generics with the openModal function but in all cases the someResult and someOtherResult always had a type of any or unknown.

If the type can somehow be infered on the functional component alone (so without using resultType) that would be even better

Note: I have my code working just fine but it’s the TypeScript that I’m concerned with.

2

Answers


  1. Generics! If I understand your problem correctly, something like this should get you what you’re looking for:

    function openModal<T extends { resultType: unknown}>(arg: T): T["resultType"]{
        if (typeof arg.resultType === "string")
            console.log("String!")
    
        if (typeof arg.resultType === 'boolean')
            console.log("Boolean!")
        return arg.resultType
    };
    
    const someResult = openModal({resultType:true})
    const someOtherResult = openModal({resultType:"Im a string!"})
    

    The T would be replaced by whatever type you would like to narrow it down to, from your example it would be React.FC

    Heres a link the playground link if you want to mess with it

    Login or Signup to reply.
  2. Unfortunately this is not currently possible in TypeScript with JSX. Even though the components SomeModal and SomeOtherModal have different types, the JSX elements <SomeModal /> and <SomeOtherModal /> have the same type, which is JSX.Element. So no matter how you write the call signature for openModal(), the argument to openModal() will not have the information you need. You’re stuck.

    There is a longstanding open feature request in GitHub at microsoft/TypeScript#14729 to allow some way of distinguishing the type of JSX elements based on their contents. Maybe someday it will be implemented, and the chance of that presumably increases with an increase in community demand. So anyone who wants to see this happen might like to go to that issue and give it a 👍. But until and unless such a feature is introduced, you can’t do this with JSX.


    You might work around it by giving up on JSX and calling createElement() instead, at which point you could use module augmentation to overload createElement() such that the information you care about is propagated:

    import React from 'react';
    declare module 'react' {
        function createElement<P extends {}, T>(
            type: React.FunctionComponent<P> & { resultType: T },
            props?: React.Attributes & P | null,
            ...children: React.ReactNode[]
        ): React.FunctionComponentElement<P> & { __resultType: T };
    }
    

    That’s just one possible way to do that; presumably __resultType will not really be present at runtime (but neither is resultType in your example code). And then your someModal could use that propagated information:

    const openModal = <T extends JSX.Element & {__resultType: any}>(element: T) => {
        return null! as T["__resultType"]; // not sure how you'd implement this
    }
    

    And finally your code behaves as expect if you give up on JSX:

    const SomeModal: React.FC & { resultType: boolean } = () => <div />;
    SomeModal.resultType = true;
    
    let someResult = openModal(React.createElement(SomeModal)); // boolean
    
    const SomeOtherModal: React.FC & { resultType: string } = () => <div />;
    SomeOtherModal.resultType = "string";
    
    let someOtherResult = openModal(React.createElement(SomeOtherModal)); // string
    

    Playground link to code

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search