2

I am currently working on integrating React in to a TypeScript based framework. Unfortunately as soon as I want to inject components, the typings are not correct anymore.

I know that this is not the way a lot of people are using React, but instead of using higher order components we want to use dependency injection as it is in our opinion the better pattern.

I have tried to do the following

import * as React from "react";
import * as ReactDOM from "react-dom";

export class Foo extends React.Component {
    public render() {
        return (<div>Bar</div>);
    }
}

export class Foobar {
    constructor(private component: Foo) {}

    public render(): void {
        const Component = this.component;

        ReactDOM.render(
            <Component />, // <Foo /> works perfectly
            document.querySelector("#root")
        );
    }
}

But this does not work unfortunately. It errors with the following output

ERROR in src/app/test.tsx(17,14): error TS2604: JSX element type 'Component' does not have any construct or call signatures.

If I try it like this

public render(): void {
    ReactDOM.render(
        React.createElement(this.component, {}),
        document.querySelector("#root")
    );
}

It results in to the following

ERROR in src/app/test.tsx(15,33): error TS2345: Argument of type 'Foo' is not assignable to parameter of type 'string | StatelessComponent<{}> | ComponentClass<{}, any>'.
  Type 'Foo' is not assignable to type 'ComponentClass<{}, any>'.
    Type 'Foo' provides no match for the signature 'new (props: {}, context?: any): Component<{}, any, any>'.

I am not really in favor of using as any in this scenario as I would love to be able to refactor Foo without having to manually check every instance if the props are passed down correctly.

I have heard from React.sfc but as far as I can tell it doesn't fix the problem I am facing.

3
  • 1
    Since you are not creating an object from Foo but passing the class itself, it seems that ComponentClass<Props> would be the type you want to use Commented Oct 25, 2018 at 13:05
  • we want to use dependency injection as it is in our opinion the better pattern - it's possible that you approach it from the wrong side. React already uses dependency injection pattern via props and a context. Commented Oct 25, 2018 at 13:10
  • Oh yeah I guess that makes sense, good thing the dependency injector can work around with that :) @estus contexts are not really dependency injection, its container aware pattern. Commented Oct 25, 2018 at 13:11

3 Answers 3

1

component name is misleading because it's expected to be a constructor, and constructors are conventionally have pascal case names. It should be typed accordingly because component: Foo means that it's an instance of Foo.

It likely should be:

export class Foobar {
    constructor(private Component: typeof Foo) {}

    public render(): void {
        ReactDOM.render(
            <this.Component />
            document.querySelector("#root")
        );
    }
}

React already uses dependency injection pattern. Dependencies can be injected with props and context API, e.g.:

ReactDOM.render(<App Foo={FooImplementation}/>, ...);
Sign up to request clarification or add additional context in comments.

4 Comments

This is only true when using JSX inside ReactDOM.render, which I am not in the second example and am using uppercase in the first example.
Not following this convention may result in mistakes, component name doesn't show the intention to get a constructor as a parameter. A developer sees component: Foo and thinks, 'ok, component is an instance of Foo, makes sense`, while it's Foo itself.
Yes I agree about the type, thats why I was asking. But the uppercase in this case doesn't make sense.
It makes sense for developers who got accustomed to this convention because private Component: Foo mistake can be spotted on sight, while private component: Foo seems ok at first sight.
0

The problem is that you have stated that your constructor takes an instance of Foo, denoted by the type Foo but you are actually passing using the class Foo denoted typeof Foo.

More specifically, change

export class Foobar {
  constructor(public component: Foo) {}
}

To

export class Foobar {
  constructor(public Component: typeof Foo) {}
}

By the way, higher order components are a dependency injection pattern. Dependency injection is nothing more than parameterization.

Comments

0

@Boy With Silver Wings gave the correct answer. I guess it feels a little bit wrong but I can totally understand why (JSX and such).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.