Carven Carven - 3 months ago 25
React JSX Question

How to call a sub component's method from a main component in React with Typescript?

I'm trying to access a component as itself so that I could call its methods, but I haven't been successful at it.

I've a simple

TestComponent
which has a
print()
method in it. I want to call that
print()
method from
MainComponent
. So I did something like this:

class TestComponent extends React.Component<IProps, IState) {

print() {
console.log('From TestComponent');
}

render() {
return <h1>Item</h1>;
}
}

class MainComponent extends React.Component<IProps, IState) {

private list:Array<TestComponent>; //This is going to throw an error in Typescript. It expects type of JSX Element.

render() {
for(let i=0; i<5; i++) list.push(<TestComponent/>); //<TestComponent> is recognised as a JSX element instead so this will throw an error
list.forEach((component) => component.print()); //Since this is JSX Element, calling print() will throw unknown function print() error.
return <h1>Main Component</h1>{list};
}
}


It turns out that I cannot use
TestComponent
as the object type in Typescript. I have to set the type of my
list
array to be of
JSX.Element
for it to work. But if I do so, I cannot "up-cast" the type to call
TestComponent
's
print()
method from
MainComponent
.

So, how can I call a sub component's method from a main component which contains the sub component?

And, in Typescript, how should I define my component's type?

Answer

This is not react is supposed to be used.
The way you control child elements is by passing props to them and not by executing methods.

For example:

interface IProps {
    toPrint: boolean;
}

class TestComponent extends React.Component<IProps, IState) {
    print() {
        console.log('From TestComponent');
    }

    render() {
        if (this.props.toPrint) {
            this.print();
        }

        return <h1>Item</h1>;
    }
}

class MainComponent extends React.Component<IProps, IState) {
    render() {
        let kids: JSX.Element[] = [];
        for (let i = 0; i < 5; i++) {
            list.push(<TestComponent toPrint={ true } />);
        }

        return <h1>Main Component</h1>{ list };
    }
}

If you need to save references to existing child elements then you need to use the Refs to Components which has a few way of doing things, for example:

class MainComponent extends React.Component<IProps, IState) {
    private list: Array<TestComponent> = [];

    render() {
        let kids: JSX.Element[] = [];
        for (let i = 0; i < 5; i++) {
            list.push(<TestComponent toPrint={ true } ref={ (t) => this.list.push(t) } />);
        }

        return <h1>Main Component</h1>{ list };
    }
}