Convert React Component to Ignition Module

My Perspective module is now setup in my development environment in IntelliJ. I found it easier to take the example perspective component project and change all the packages and file names, than it was to take a skeleton project and try to build it up. see this post https://forum.inductiveautomation.com/t/generate-skeleton-project-for-perspective-component/68829/5

Now I am trying to figure out how to convert a react component I made using static files and make a Perspective component out of it. This is my react component

'use strict';

const e = React.createElement;

class KeyCapture extends React.Component {
  constructor(props) {
    super(props);
    this.state = { textInput: "" };

    this.handleKeyDown = this.handleKeyDown.bind(this);
  }

  handleKeyDown(e){
    e.preventDefault();

    if(/^[ -~]$/.test(e.key)
      || e.key == " "
    ){
      e.stopPropagation();
      this.setState(prevState => ({
        textInput: prevState.textInput+e.key
      }));
    }
    else if(e.key == "Enter"){
      e.stopPropagation();
      this.setState({
        textInput: ""
      });
    }
  }

  render() {
    return this.state.textInput;
  }

  componentDidMount(){
    window.addEventListener("keydown", this.handleKeyDown);
  }

  componentWillUnmount(){
    window.removeEventListener("keydown", this.handleKeyDown);
  }
 }

const domContainer = document.querySelector('#key_capture_container');
const root = ReactDOM.createRoot(domContainer);
root.render(e(KeyCapture));

From the examples in the SDK I do not see how you can register a handlKeyDown(e) event. Also it seems like the syntax is a little different from what I was able to write from the react website to what I am seeing in the .tsx file. Is there some documentation I can be pointed to that would show me how to bind to certain javascript events perspective/.tsx file?

You should still be able to register event listeners in componentDidMount and componentWillUnmount. window is a global variable in browser based JS, so it's always available.
You don't manually create an instance of your component (Perspective will do that for you), and you use the Perspective React component as the base class, but the rest of the code should be exactly the same.

Thanks. It did not seem to like constructor(props) so changed how the state is initialized based on the TagCounter example. And then since I did not have the constructor anymore I followed the way the binding is done in the Messenger example with the @bind.

Here is what I have so far that works. it prints to the console in the designer!

export class KeyCapture extends Component<ComponentProps<KeyCaptureProps>, any> {
    state: KeyCaptureState = {
        textInput: ""
    };

    @bind
    handleKeyDown(e: KeyboardEvent): void {
        console.log("key down", e.key);
    }
    componentDidMount() {
        window.addEventListener("keydown", this.handleKeyDown);
    }
    componentWillUnmount() {
        window.removeEventListener("keydown", this.handleKeyDown);
    }

    render() {
        //const { props: { text }, emit } = this.props;
        const { emit } = this.props;

        return (
            <div
                {...emit()}
            >{this.state.textInput}
            </div>
        );
    }
}

Now when I add the rest of my code in the handleKeyDown I am getting an error

TS7006: Parameter 'prevState' implicitly has an 'any' type

export class KeyCapture extends Component<ComponentProps<KeyCaptureProps>, any> {
    state: KeyCaptureState = {
        textInput: ""
    };

    @bind
    handleKeyDown(e: KeyboardEvent): void {
        e.preventDefault();

        if(/^[ -~]$/.test(e.key)
            || e.key == " "
        ){
            e.stopPropagation();
            this.setState(prevState => ({
                textInput: prevState.textInput+e.key
            }));
        }
        else if(e.key == "Enter"){
            e.stopPropagation();
            this.setState({
                textInput: ""
            });
        }
    }
    componentDidMount() {
        window.addEventListener("keydown", this.handleKeyDown);
    }
    componentWillUnmount() {
        window.removeEventListener("keydown", this.handleKeyDown);
    }

    render() {
        //const { props: { text }, emit } = this.props;
        const { emit } = this.props;

        return (
            <div
                {...emit()}
            >{this.state.textInput}
            </div>
        );
    }
}

Add types to your arrow function?

            this.setState(prevState: KeyCaptureState => ({
                textInput: prevState.textInput+e.key
            }));

Then I get

TS2304: Cannot find name 'prevState'

Looks like I need () around the parameters. Now it compiles.

this.setState((prevState: KeyCaptureState) => ({
    textInput: prevState.textInput+e.key
}));
1 Like