javascriptreactjstypescripttransclusion

React transclusion and communication with container component


I'm looking for the proper pattern/way to develop this with React, but I didn't find anything relevant/elegant so far.

I basically want to write a form engine. For each input of the form, I want some generic behaviors.

I've read on React documentation that inheritance isn't the proper way to go; the good way to go is to design a generic component and specialize it by composition.

In my top component, I want to write something like:

<Form>
    <TextInput .../>
    <TextInput .../>
    <EmailInput .../>
</Form>

Each type of input must basically do always the same things: example: check this value against its validators, etc.

So, I've designed a generic component FormInput which contains all those standard behaviors. When I write my TextInput component, here is what it looks like:

export default class TextInput extends React.Component {

    constructor(props) {
        super(props);
    }

    render() {
        return (
            <FormInput>
                <input name={this.props.name} 
                       type='text'
                       onChange={this.onChange}
                       onBlur={this.dirty}
                       value={this.state.value}
                />
            </FormInput>
        );
    }

}

Now, the trouble is that this.onChange or this.dirty are the standard behaviors located in the FormInput component, so obviously, I cannot access directly to them like that...

What is the proper way to connect the transcluded content to its container component?

EDIT

Just to clarify and sum up the goal, I basically want to make a generic component and a transcluded content/template. The issue with this is that I need to bind the specific DOM template (which is in the specific component) to the generic handlers (which are in the generic component).

Thanks by advance!


Solution

  • I've finally figured it out!

    Here is my solution:

    export default class TextInput extends React.Component {
    
        constructor(props) {
            super(props);
        }
    
        template(state, formInput) {
            return (
                 <input name={this.props.name} 
                        type='text'
                        onChange={formInput.onChange}
                        onBlur={formInput.dirty}
                        value={state.value}/>
            );
        }
    
        render() {
            return (
                <FormInput {...this.props} template={this.template} />
            );
        }
    
    }
    

    It was finally easier than expected, but I didn't use transclusion.

    The FormInput component hosts everything, from the state to the common code/behaviors (like dirty or onChange).

    The TextInput component only instanciates a FormInput and hosts a function called template, that takes the remote state and the generic FormInput component itself to access its functions.

    Then in the FormInput, i've got the following:

    render() {
        return this.props.template(this.state, this);
    }
    

    The generic input component calls the template function and passes in what is needed for the rendering.

    This way, I uncouple the view from its behavior.

    Hope you'll like it and that it will helps someone later ;)