skip to Main Content

I’m trying to wrap my head around how this is supposed to work. I have a base class with props. Then I have an interface for the props for the child class. I am getting the typescript error

Property 'lineWidth' does not exist on type 'Readonly<BasicChartProps<LineChartProps<T>>>'.ts(2339)

here is my sample code example

import React from 'react';

export interface BasicChartProps<T> {
  data: T[];
  x_key: keyof T;
  y_key: keyof T;
}

export type BasicChartState<T> = {
  data: T[];
  x_key: keyof T;
  y_key: keyof T;
  x_margin: number;
  y_margin: number;
};

export class BasicChart<T> extends React.Component<
  BasicChartProps<T>,
  BasicChartState<T>
> {
  state: BasicChartState<T> = {
    x_margin: 50,
    y_margin: 50,
    data: this.props.data || [],
    x_key: this.props.x_key,
    y_key: this.props.y_key,
  };

  render(): React.ReactNode {
    return (
      <div>
        <div>x_margin: {this.state.x_margin}</div>
        <div>y_margin: {this.state.y_margin}</div>
      </div>
    );
  }
}

export interface LineChartProps<T> extends BasicChartProps<T> {
  lineWidth: number;
}

export class LineChart<T> extends BasicChart<LineChartProps<T>> {
  render(): React.ReactNode {
    const {lineWidth} = this.props;
    return <div>lineWidth: {lineWidth}</div>;
  }
}


I’m hoping someone can shed some light on what I am doing wrong.

2

Answers


  1. Kind of hacky, but you can pass LineChartProps as another template variable:

    import React from "react";
    
    export type BasicChartProps<T, K> = K & {
      data: T[];
      x_key: keyof T;
      y_key: keyof T;
    }
    
    export type BasicChartState<T> = {
      data: T[];
      x_key: keyof T;
      y_key: keyof T;
      x_margin: number;
      y_margin: number;
    };
    
    export class BasicChart<T, K> extends React.Component<
      BasicChartProps<T, K>,
      BasicChartState<T>
    > {
      state: BasicChartState<T> = {
        x_margin: 50,
        y_margin: 50,
        data: this.props.data || [],
        x_key: this.props.x_key,
        y_key: this.props.y_key
      };
    
      render(): React.ReactNode {
        return (
          <div>
            <div>x_margin: {this.state.x_margin}</div>
            <div>y_margin: {this.state.y_margin}</div>
          </div>
        );
      }
    }
    
    export interface LineChartProps {
      lineWidth: number;
    }
    
    export class LineChart<T> extends BasicChart<T, LineChartProps> {
      render(): React.ReactNode {
        const x = this.props;
        x.
        return <div>lineWidth: {lineWidth}</div>;
      }
    }
    
    Login or Signup to reply.
  2. Problem

    Passing LineChartProps to BasicChart‘s type parameter passes it to BasicChartProps‘s type parameter which then gets used on various properties on that interface. Here’s a visualisation:

    class LineChart<T> extends BasicChart<LineChartProps<T>> { ... }
                      ____________________________|
                     |
                     v
    class BasicChart<T>
                     |______________________
                                            |
                                            v
    extends React.Component<BasicChartProps<T>, BasicChartState<T>> { ... }
                               _____________|
                              |
                              v
    interface BasicChartProps<T> {
             _________________|
            |                 |
            v                 |
      data: T[];    __________|
                   |          |
                   v          |
      x_key: keyof T;         |
                    __________|
                   |
                   v
      y_key: keyof T;
    }
    

    Solution

    I suspect the above isn’t what was intended and that you’re trying to get the type of LineChart‘s type parameter passed all the way down to BasicChartProps. Like this?

                     ____________________________________
                    |                                    |
                    |                                    v
    class LineChart<T> extends BasicChart<LineChartProps<T>> { ... }
                              ___________________________|
                             |
                             v
    interface LineChartProps<T>
                            _|
                            |  
                            v
    extends BasicChartProps<T>
                            |_
                              |
                              v
    interface BasicChartProps<T> {
             _________________|
            |                 |
            v                 |
      data: T[];    __________|
                   |          |
                   v          |
      x_key: keyof T;         |
                    __________|
                   |
                   v
      y_key: keyof T;
    }
    

    To get that to work, I think something like this would be ideal:

    class BasicChart<Props extends BasicChartProps<?>> extends React.Component<
                       |                           ʌ
    /* Somehow extract the type parameter */       |
                      _|___________________________|
                      |
      Props,          |
                      v
      BasicChartState<?>
    > { ... }
    

    I think it’s possible by updating BasicChart to the following:

    type FirstGenericTypeParameter<Props extends BasicChartProps<any>> = Props extends BasicChartProps<infer T> ? T : never;
    
    export class BasicChart<
      Props extends BasicChartProps<T>,
      T = FirstGenericTypeParameter<Props>
    > extends React.Component<
      Props,
      BasicChartState<T>
    > { ... }
    

    The infer keyword is used to extract the (first) generic type parameter from Props and assign it to T. T is then passed to BasicChartProps and BasicChartState.

    Here’s a demo in TypeScript Playground. At the end is the following to test things work as expected (do play around yourself to verify things work to your own expectations):

    class FooLineChart extends LineChart<{ foo: string }> {
      render() {
    
        this.props.data[0].foo // string
        this.props.x_key // "foo"
        this.props.y_key // "foo"
        this.props.lineWidth // number
    
        this.state.data[0].foo // string
        this.state.x_key // "foo"
        this.state.y_key // "foo"
        this.state.x_margin // number
        this.state.y_margin // number
    
        return (<></>);
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search