mercredi 8 septembre 2021

React, TypeScript - Using non-React objects to inject components into component tree

I'm working on a React Typescript project. A very simplified version of the project is below. I'm trying to use more traditional polymorphism here where I have components returned from vanilla Typescript objects (not React components) that are rendered in the component tree. The reason I want to do this is so that I can have polymorphic classes that I change at runtime and that manage their own state and business logic.

import React, { useEffect } from "react";

class ClickCounter {
  private count: number;
  constructor() {
    this.count = 0;
  }
  IncrementCount() {
    this.count += 1;
  }
  GetCount(): number {
    return this.count;
  }
}

interface Operation {
  HandleMouseDown(event: React.MouseEvent<HTMLDivElement>): void;
  GetComponents(): JSX.Element[];
}

class ClickCounterOperation implements Operation {
  private clickCounter: ClickCounter;
  constructor() {
    const counter: ClickCounter = new ClickCounter();
    this.clickCounter = counter;
  }

  HandleMouseDown(_: React.MouseEvent<HTMLDivElement>): void {
    this.clickCounter.IncrementCount();
  }

  GetComponents(): JSX.Element[] {
    const count: number = this.clickCounter.GetCount();
    return [<div>you have clicked {count} times</div>];
  }
}

export type AppState = {
  currentOperation: Operation;
};

export class App extends React.Component<{}, AppState> {
  constructor(props = {}) {
    super(props);
    const initialOperation: Operation = new ClickCounterOperation();
    this.state = {
      currentOperation: initialOperation,
    };
    this.HandleMouseDown = this.HandleMouseDown.bind(this);
  }

  HandleMouseDown(event: React.MouseEvent<HTMLDivElement>) {
    console.log("Dispatching mouse down event to current operation");
    this.state.currentOperation.HandleMouseDown(event);
  }

  render() {
    return (
      <div className="App" onMouseDown={this.HandleMouseDown}>
        {this.state.currentOperation.GetComponents()}
        <div>some other stuff to show</div>
      </div>
    );
  }
}

export default App;

In the example above everything will render initially, but not after the count is updated. This is because react has no way of knowing that the state has changed and that a rerender is needed. What I'm currently doing is forcing React to rerender by passing down a RefreshOperationState callback to the Operation object that will call the App.setState() method but this feels very ugly and I don't want to do this.

Any way to achieve this kind of traditional polymorphism with React and have non-React objects inject components into the component tree and have the components update when appropriate? I understand what I am trying to do is not following the common React patterns of using Flux/Redux and having all/most app state in a store/s, but I'd like to make this app in a less functional and more OOP pattern where objects store their own state and are called polymorphicly.

Any suggestions?

Aucun commentaire:

Enregistrer un commentaire