Accessing child instances of a component in React

in javascript •  4 years ago 

Original creation date: 19th December 2020
Note: Originally I posted this on my Wordpress Blog (https://1337codersblog.wordpress.com), but I decided to switch to steemit, so I copied it from there.

Accessing child instances of a component in React

I currently work on a widget to modify (add, update, delete) entries of the database of my trading system by the user. The widget shall work for any table of the database by just specifying some parameters. It will query the schema from the frontend service (which then relays the schema query to the database service) and render the needed input fields according to the queried schema. So this means, that an input fields can have different types, depending on the schema:
– Numeric: only accepts numbers as input
– String: accepts any text
– Enum: only accepts predefined values. It will be rendered as selection list
– Object: only accepts values that are a primary key of the entity the property references to. This will be rendered as a selection list, which has the value of the primary key set as the “value” HTML attribute and a presentation of the related entry as the content. So the user selects the entry he wants the property to point to and when the form is submitted, the related id (given by the “value” attribute) ist transmitted.

After some tries that became bad maintainable code I decided to modularize the Javascript code of the widget into some React components:
– DatabaseTableModifyEntryWidget: this renders the whole widget
– InputField: this renders input fields that shall result in an HTML “input” tag
– SelectionList: this renders selection list and results in an HTML “select” tag
– Button: this renders a button
InputField, SelectionList and Button are instantiated within the “render” method of the DatabaseTableModifyEntryWidget.

Actions that change the components are done by functions that are bound to the instance of the related component. For example if I want to change the caption of a Button I call the “setContent” method of the Button’s instance, which will modify the “content” property of the Button’s state and triggers a re-render.

If I now want to change the captions of all buttons the DatabaseTableModifyEntryWidget contols, I have to add a “changeCaptions” method to the DatabaseTableModifyEntryWidget class. The implementation of the function now needs to iterate over all buttons that are controlled by the class and call the “setContent” component of each button instance.

The problem is that a call to React.createElement, which is used for rendering a component, does not return the created instance. I could use ReactDOM.render, this will return the instance, but it would require to render a container element for each Button within the “render” function of DatabaseTableModifyEntryWidget and then calling ReactDOM.render to attach a Button to the DOM of the widget and store the instance somewhere. But this way is bad code and error prone, because I would have to ensure that the call of ReactDOM.render for the Button is made after the “render” function of the widget is finished.

So I found a more simple and cleaner solution: I added a property “onInit” to the child components (Button, InputField, SelectionList). This property is a function that is called within the constructor of the child component and takes the instance of the child as parameter:

class Button extends React.Component{
    constructor(props) {
        super(props);

        if (this.props.onInit != null){
            this.props.onInit(this);
        }

        this.state = {
            content: props.content,
            activity: {
                processing: false,
                successful: false,
                failed: false
            },
            disabled: false
        }
    }
...

The widget implements the “onInit” function. The implementation takes the instance parameter of the Button and stores it. When creating the Button component, the implementation of the “onInit” function is bound by the widget to the “onInit” property of the component:

class DatabaseTableModifyEntryWidget extends React.Component{
    constructor(props) {
        super(props);

        this.buttonInstances = {};
        this.inputFieldInstances = {};

        this.state = {
            buttons: this._createButtons(this.props.buttons),
            inputElements: this._createInputElements(this.props.fields),
            translation: this.props.translation
        }
    }

    _createButtons(buttons) {
        const createdButtons = [];

        buttons.forEach(button => {
            const onClickFunction = button.onClickFunction;
            createdButtons.push(React.createElement(Button, {
                id: button.id,
                content: button.content,
                onInit: (buttonInstance) => {
                    this.buttonInstances[button.id] = buttonInstance;
                },
                onClickFunction: (buttonInstance) => {
                    onClickFunction(this, buttonInstance);
                },
                defaultCss: button.defaultCss,
                onProcessingCssClass: button.onProcessingCssClass,
                onSuccessfulCssClass: button.onSuccessfulCssClass,
                onFailedCssClass: button.onFailedCssClass
            }, null));
        });

        return createdButtons;
    }
...

So now I can implement the “setCaptions” function of DatabaseTableModifyEntryWidget:

setCaptions(captions){
    iterateObjectPropertiesSorted(captions, (buttonID)=>{
        this.buttonInstances[buttonID].setCaption(captions[buttonID]);
    });
}
Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!