Reusable form in React

From Logic Wiki
Jump to: navigation, search

Creating Form

in common folder we create a new file named form.jsx

import React, { Component } from 'react';
import Joi from 'joi-browser';
import Input from './input';

class Form extends Component {
  state ={
    data:{},
    errors:{}
  };
 
  validate = () => {
    const options = { abortEarly:false}; 
    const { error } =  Joi.validate(this.state.account, this.schema, options);
    if(!error) return null
  
    const errors = {};
    for (let item of result.error.details)
    {
        errors[item.path[0]] = item.message;
    }
    return errors;
  };
  
  validateProperty = ({name, value}) => {
    const obj = { [name]: value };
    const schema = { [name]: this.schema[name] };
    const {error} =  Joi.validate(obj, schema)
 
    return error ? error.details[0].message : null;
  };

  handleSubmit = e => {
    e.preventDefault();
    
    const errors = this.validate();
    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.valdiate()} className="btn btn-primary">
           {label}
         </button>
     );
   }
    
    renderInput(name, label, type= 'text') {
       const { data, errors } = this.state;

       return(
         <Input 
               type={type}
               name={name}
               value={data[name]}
               label={label}
               onChange={this.handleChange}
               error={errors[name]}
            />
       );
    }

    renderSelect(name, label, options){
      const {data, errors } = this.state;
      return(
        <Select 
           name={name}
           value={data[name]}
           label={label}
           options={options}
           onChange={this.handleChange}
           error={errors[name]}
         />
       ); 
    };
}
export default Form

We define data and errors in state as a (user defined) naming convention

Using Form as base class

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 = () => {
     // call the server 
     // 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>
     );
  }
}

Input.jsx

import React from "react";

const Input = ({name, label, value, error, onChange, type}) => {
  return (
   <div className="form-group">
     <label htmlFor={name}>{label}</label>
     <input 
       value={value}
       onChange={onChange}
       id={name}
       name={name}
       type={type}
       className="form-control"
     />
    {error && <div className="alert alert-danger">{error}</div>}
   </div>
  );
};

export defaiult Input;

Refactored Input.jsx

import React from "react";

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

export defaiult Input;

Select.jsx

import React from "react";

const Select = ({name, label, options, error, ...rest}) => {
  return (
   <div className="form-group">
     <label htmlFor={name}>{label}</label>
     <select name={name} id={name} {...rest} className="form-control">
        <option value="" />
        {options.map(option => (
          <option key={option._id} value={option._id}> {option.name} </option>
         ))};
     <select/>
    {error && <div className="alert alert-danger">{error}</div>}
   </div>
  );
};

export defaiult Select;