Search code examples
formsreactjsinputcomponentscomposition

React JS - Composing generic form with dynamic childrens


I am just a beginner in reactjs. I feel so good about using it, but I have been stuck for a while on a concept that I wanna implement, that would solve me whole lot of other issues.

Concept

  • I want to create a form component that can have dynamic children passed to it. It won't be a specific form component like LoginForm, ContactForm etc. Rather it would just be Form.

Approach 1

class LoginPage extends Rect.Component {

    constructor(props) {
        super(props)

        this.submit = this.submit.bind(this);
        this.validate = this.validate.bind(this);
        this.reset = this.reset.bind(this);
    }

    submit(...args) {
        this.refs.form.submit(args);
    }

    validate(...args) {
        this.refs.form.validate(args);
    }

    reset(...args) {
        this.refs.form.reset(args);
    }

    render() {
        return (
        <LoginPage>
            <Form ref="form" onSubmit={this.submit}>
                <Input value="email" pattern="/email_pattern/" onValidate={this.validate} />

                <Input value="password" pattern="/password_pattern/" onValidate={this.validate} />

                <Button value="submit" action={this.submit} />
                <Button value="reset" action={this.reset} />
            </Form>
        </LoginPage>
    }
}
  • Input onChange calls the validate function that just passes on the args to the Form's validate function. For the Form to know if all it's children's are validated. I pass isValid and targetInputComponent as args to the form.
  • Button Submit onClick calls the submit function likewise and LoginPage (acts as middleware) passes the call to the Form component. Form check it's state for inValid inputs etc.
  • Button Reset onClick call is passed to the Form component likewise. But how do the form handle this reset functionality? Input's value is controlled by LoginPage. Form can't change the input's value.

Aproach 2

What I did was add the input's data and validity to the LoginPage state. Now both Form and Inputs just call the Login Page to update it's state. Form and Inputs components are using this state as prop.

class LoginPage extends React.Component {

    constructor(props) {
        super(props)

        this.state = {
            formSchema: this.initSchema()
        }

        this.validateField = this.validateField.bind(this);
        this.submitForm = this.submitForm.bind(this);
    }

    initSchema() {
        const formSchema = {
            email: {
                isValid: false,
                value: ''
            },
            password: {
                isValid: false,
                value: ''
            },
            password2: {
                isValid: false,
                value: ''
            }
        }

        return formSchema;
    }

    validateField(dataObj, targetComponent) {

        const formSchema = Object.assign(this.state.formSchema, dataObj);

        this.setState({ formSchema })
    }

    submitForm(isSuccess) {
        console.log(isSuccess);
        console.log('Form Submit: ', this.state.formSchema);

        throw new Error('Submition Error');
    }

    reset() {
        // Loop through each object in formSchema and set it's value to empty, inputs will validate themselves automatically.
    }

    render() {
        return <div>
            <Form ref="formLogin" className="auth-form" onSubmit={this.submitForm} formSchema={this.state.formSchema}>
                <h1>
                    Switch Accounts
                </h1>
                <Input type="email" name="email" onValidate={this.validateField} value={this.state.formSchema.email.value}
                isValid={this.state.formSchema.email.isValid}/>

                <Input type="password" name="password" onValidate={this.validateField} value={this.state.formSchema.password.value}
                isValid={this.state.formSchema.password.isValid}/>

                <Button value="Submit" type="submit"  />
            </Form>
        </div>
    }
}

Problem

This approach is making the LoginPage Component quite messy. How will the LoginPage component will handle the forms if I have more than 1 form on the page? There will be even more features to LoginPage like Lists, Grid, Modals etc. This LoginPage shouldn't Handle these situations. Form should be responsible for all the inputs, submition, etc functionality. This form component should be generic to all types of forms. I don't want to create feature forms like LoginForm, ContactForm, etc.

The solution to this issue will aid me a lot in whole lot of other components.


Solution

  • Your approach 2 is the standard way of handling this problem.

    Why wouldn't you create a separate <LoginForm>? Doing so can abstract the fields and validation away from other unrelated functionalities on your page.

    If later you need a <ContactForm> or any other type of form, it will have different fields and different validation logic, meaning you'll need to build it out regardless.

    Short of using something like redux, you're on the right track.