How can I convert a Class Component which extends another Class component in a Functional Component in ReactJS?

How can I convert a Class Component which extends another Class component in a Functional Component in ReactJS?

input.jsx [Functional Component]

const Input = ({ name, label, error, ...rest }) => {
    return (
        <div className="mb-3">
            <label htmlFor={name} className="form-label">
                {label}
            </label>
            <input
                autoFocus
                {...rest}
                id={name}
                name={name}
                className="form-control"
            />
            {error && <div className="alert alert-danger">{error}</div>}
        </div>
    )
}

export default Input

form.jsx [Class Component]

import React, { Component } from "react"
import Input from "./input"
import Joi from "joi"

class Form extends Component {
    state = {
        data: {},
        errors: {}
    }

    validate = () => {
        const options = { abortEarly: false }
        const schemaJoi = Joi.object(this.schema)
        const { error } = schemaJoi.validate(this.state.data, options)
        if (!error) return null

        const errors = {}
        error.details.map(item => (errors[item.path[0]] = item.message))
        return errors
    }

    validateProperty = ({ name, value }) => {
        const obj = { [name]: value }
        const schema = {
            [name]: this.schema[name]
        }
        const schemaJoi = Joi.object(schema)
        const { error } = schemaJoi.validate(obj)
        return error ? error.details[0].message : null
    }

    handleSubmit = e => {
        e.preventDefault()

        const errors = this.validate()
        console.log(errors)
        this.setState({ errors: errors || {} })
        if (errors) return

        this.doSubmit()
    }

    handleChange = ({ currentTarget: input }) => {
        const errors = { ...this.state.errors }
        const errorMessage = this.validateProperty(input)
        if (errorMessage) errors[input.name] = errorMessage
        else delete errors[input.name]

        const data = { ...this.state.data }
        data[input.name] = input.value
        this.setState({ data, errors })
    }

    renderButton = label => {
        return (
            <button disabled={this.validate()} className="btn btn-primary">
                {label}
            </button>
        )
    }

    renderInput = (name, label, type = "text") => {
        const { data, errors } = this.state
        return (
            <Input
                name={name}
                label={label}
                error={errors[name]}
                type={type}
                value={data[name]}
                onChange={this.handleChange}
            />
        )
    }
}

export default Form

loginForm.jsx [Class Component which extends the other]

import Joi from "joi"
import Form from "./common/form"

class LoginForm extends Form {
    state = {
        data: { username: "", password: "" },
        errors: {}
    }

    schema = {
        username: Joi.string().required().label("Username"),
        password: Joi.string().required().label("Password")
    }

    doSubmit = () => {
        console.log("Submitted")
    }

    render() {
        return (
            <div>
                <h1>Login</h1>
                <form onSubmit={this.handleSubmit}>
                    {this.renderInput("username", "Username")}
                    {this.renderInput("password", "Password", "password")}
                    {this.renderButton("Login")}
                </form>
            </div>
        )
    }
}

export default LoginForm

I already know how to convert a simple Class Component to a Stateless Functional Component but what I don’t know is how to convert a Class Component which extends another Class Component.

Please, may you explain me how?

1 Like

Hi,

Thanks for your reply but, as I wrote, I already know how to convert a Class Component to a Functional one. What I don’t know is how to convert a Class Component which extends another Class Component.

I don’t know if there are other ways around, but using HOOKS should be the most common way to solve this issue.

Check this link out , I believe it can help a lot :

Thanks but I think this don’t solve the issue. Mosh, where are you? Could you answer?

1 Like

Hi.

I am actually wondering about the same thing.

The form as presented in the React course is awesome. It provides a Form component with all the common logic and ways for derived components to be customised. This makes the code clean and easy to maintain.

In the few months after I finished the course I learnt hooks and functional components is my new way to go.

The counterpart to inheritance is composition. So I thought I’d read about it but I am not convinced yet it would be a good solution.

I just saw a video on YT about the useForm hook. It is very nice. Validation logic is inline which differs from what we learnt with Mosh course.
You can’t benefit from the renderInput() functionality and alike though. So you still need to write the code entirely in every form.

I would really enjoy to know how we could get the same level of functionality / ease of {use | maintenance}.

Best regards.

1 Like

I just started to play with HOC.

import React from 'react'

const withForm = (Component) => {

    const renderInput = (name, value, label, placeholder) =>
        <>
            <label htmlFor={`${name}_id`}>{label}</label>
            <input id={`${name}_id`} name={name} placeholder={placeholder} defaultValue={value} /><br />
        </>

    const formMethods = { renderInput };
    return (props) => <Component formMethods={formMethods} {...props} />
}

export default withForm;

On an actual form to be made.

import React from 'react';
import withForm from '../../HOC/withForm';

const PrimoForm = (props) => {
    const { renderInput } = props.formMethods;
    return (
        <div>
            <h1>PrimoForm</h1>
            {renderInput('firstname', 2.3, 'First Name', '')}
            {renderInput('lastname', 'Reactson', 'Last name')}
            {renderInput('email', 2, "Email but I chose to send number")}
        </div>
    )
}

export default withForm(PrimoForm);

image

The thing is we must pass the methods via props and of course there would be more than in this sample so I’d rather pass them in a global object instead of millions of separate props.

I did not use useForm hook but the concept of providing a set of methods looks promising.

So the <Form /> component shall be converted to HOC with all the data validation logic and then actual forms should be wrapped in that HOC.