Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to overwrite React.Component state type from inherited class?

I have a component class that extends another component, and I'm trying to figure out how to overwrite the type of the state that I'm inheriting from.

Here's an example:

class MyComponent extends React.Component<SomeProps, SomeState> {
  // ...
}

class ExtendedComponent extends MyComponent {
  // How to overwrite SomeState?
  // This component needs to store a different model of state.
}

I need ExtendedComponent to use a different interface for its state, but I can't quite figure out how to accomplish this.


EDIT: Getting somewhere now!

However, now Parent is getting stuffed with all kinds of errors pertaining to state modifications. Here's what I've got so far:

interface ParentProps {
  a: string;
}

interface ParentState {
  b: string;
}

class Parent<P, S> extends React.Component<ParentProps & P, ParentState & S> {
  constructor(props) {
    super(props);

    // Type '{ b: "hello"; }' is not assignable to type 'Readonly<ParentState & S>'
    this.state = {
      b: 'hello',
    };
  }

  aFunction(): void {
    /*
      Argument of type '{ b: "testing"; }' is not assignable to parameter of type '(ParentState & S) | ((prevState: Readonly<ParentState & S>, props: Readonly<ParentProps & P>) => (ParentState & S) | Pick<ParentState & S, "b">) | Pick<ParentState & S, "b">'.
  Type '{ b: "testing"; }' is not assignable to type 'Pick<ParentState & S, "b">'.
    Types of property 'b' are incompatible.
      Type '"testing"' is not assignable to type 'string & S["b"]'.
        Type '"testing"' is not assignable to type 'S["b"]'.
    */
    this.setState({
      b: 'testing',
    });
  }
}

interface ChildProps {
  c: string;
}

interface ChildState {
  d: string;
}

class Child extends Parent<ChildProps, ChildState> {
  constructor(props) {
    super(props);

    // This is what I'm after -- and TypeScript doesn't complain :)
    this.state = {
      b: 'hello',
      d: 'world',
    };
  }
}

EDIT 2:

In retrospect, nearly two years later:

Extending another component class is not recommended by the maintainers of React. This is for good reason, as I had to maintain this approach in a production application which was abysmal! I ended up re-writing a lot of code using higher-order components and custom hooks.

There's a reason that this use-case isn't very supported, avoid creating complication caused by over-engineering your components! Imagine trying to explain your tangled mess of inheritance to another developer.

Just because it sounds cool and you can do it, doesn't mean you should. I highly recommend using functional components, and if you need shared functionality, use higher-order functions, and/or custom hooks.

like image 867
Kyle Avatar asked Oct 16 '22 12:10

Kyle


1 Answers

I'm guessing you don't want to replace the props and state, because you probably need a common interface to make the parent class work. So you could make your parent class generic, and then merge in the additional pieces of props/state.

class MyComponent<P, S> extends React.Component<ParentProps & P, ParentState & S> {}
//                ^ Generic P is for child props, Generic S is for child state

Example in use:

type ParentProps = { a: number }
type ParentState = { b: number }
class MyComponent<P, S> extends React.Component<ParentProps & P, ParentState & S> {}
let parentComponent = <MyComponent a={1} />

type ChildProps = { c: number }
type ChildState = { d: number }
class ExtendedComponent extends MyComponent<ChildProps, ChildState> {}
let childComponent = <ExtendedComponent a={1} c={2} />
like image 97
Alex Wayne Avatar answered Oct 20 '22 23:10

Alex Wayne