Skip to main content
3 of 3
edited tags; edited title
200_success
  • 145.7k
  • 22
  • 191
  • 481

React component wrappers for form fields with 90% similar code

I have a number of these components, but will show just two as an example. They're very similar, but the differences are enough to where splitting them out felt needed. What I really need is some help. Is this the right approach, or would I be better served somehow combining them to reduce duplicate code? I have similar components for toggles like radios and checkboxes, as well as textarea that handle both plain text and markdown fields, etc.

Other feedback would also be appreciated :)

Textfield wrapper.

'use strict'

import React, { Component } from 'react'
import { FieldText, FieldLabel, FieldDescription, FieldErrorMessage } from './'
import PropTypes from 'prop-types'

class FieldTextWrap extends Component {
  static propTypes = {}

  constructor(props) {
    super(props)
    this.state = {
      value: '',
      isValid: true
    }
  }

  _handleChange = value => {
    const { formatter } = this.props
    const formattedValue = !!formatter ? formatter(value) : value
    this.setState({ value: formattedValue })
  }

  componentDidMount() {
    const {
      required,
      type,
      value,
      initialValue,
      dispatch,
      handleBlur,
      isForm,
      formId,
      id
    } = this.props

    !!initialValue && this.setState({ value: initialValue })
    !initialValue && !!value && this.setState({ value })

    const fieldData = {
      id,
      value,
      type,
      required,
      formId,
      valid: true
    }

    return isForm && dispatch(handleBlur(null, dispatch, fieldData))
  }

  componentDidUpdate(prevProps, prevState) {
    const { validationRule, required } = this.props
    const { value } = this.state
    const didUpdate = prevState.value !== value

    // validate and update
    const isValid = !!validationRule
      ? validationRule.func({
          field: { required, value },
          tests: validationRule.tests,
          options: validationRule.options
        })
      : true
    didUpdate && this.setState({ isValid })
  }

  componentWillUnmount() {
    const { removeFormField, id, isForm } = this.props

    isForm && removeFormField(id)
  }

  render() {
    const {
      className = '',
      description,
      fieldGroup = '',
      formName,
      formId,
      hideDescription,
      hideLabel,
      id,
      label,
      required,
      requireDefault,
      type,
      validationText: message = ''
    } = this.props

    const classes = `form-item ${formName}__${id} ${fieldGroup} ${type}-field ${className} ${type} text-type`
    const value = this.state.value
    const isValid = this.state.isValid

    const fieldData = {
      id,
      value,
      type,
      required,
      formId,
      valid: isValid,
      message
    }

    return (
      <div className={classes}>
        <FieldLabel
          name={id}
          label={label}
          required={required}
          requireDefault={requireDefault}
          hidden={hideLabel}
        />

        <div className={`${type}-field__wrapper`}>
          {isValid ? (
            <FieldDescription
              name={id}
              description={description}
              hidden={hideDescription}
            />
          ) : (
            <FieldErrorMessage name={id} message={message} />
          )}

          <FieldText
            {...this.props}
            value={value}
            handleChange={this._handleChange}
            fieldData={fieldData}
          />

          {type === 'date' && <div className="date-field__icon" />}
        </div>
      </div>
    )
  }
}

export default FieldTextWrap

Select Wrapper

'use strict'

import React, { Component } from 'react'
import {
  FieldSelect,
  FieldLabel,
  FieldDescription,
  FieldErrorMessage
} from './'
import PropTypes from 'prop-types'

class FieldSelectWrap extends Component {
  static propTypes = {}

  constructor(props) {
    super(props)
    this.state = {
      value: '',
      isValid: true
    }
  }

  _handleChange = value => this.setState({ value })

  componentDidMount() {
    const {
      required,
      type,
      value,
      initialValue,
      dispatch,
      handleBlur,
      isForm,
      formId,
      id
    } = this.props

    !!initialValue && this.setState({ value: initialValue })
    !initialValue && !!value && this.setState({ value })

    const fieldData = {
      id,
      value,
      type,
      required,
      formId,
      valid: true
    }

    return isForm && dispatch(handleBlur(null, dispatch, fieldData))
  }

  componentDidUpdate(prevProps, prevState) {
    const { validationRule, required } = this.props
    const { value } = this.state
    const didUpdate = prevState.value !== value

    // validate and update
    const isValid = !!validationRule
      ? validationRule.func({
          field: { required, value },
          tests: validationRule.tests,
          options: validationRule.options
        })
      : true
    didUpdate && this.setState({ isValid })
  }

  componentWillUnmount() {
    const { removeFormField, id, isForm } = this.props

    isForm && removeFormField(id)
  }

  render() {
    const {
      className = '',
      description,
      fieldGroup = '',
      formName,
      formId,
      hideDescription,
      hideLabel,
      id,
      label,
      required,
      requireDefault,
      type,
      validationText: message = ''
    } = this.props

    const classes = `form-item ${formName}__${id} ${fieldGroup} ${type}-field ${className} ${type}`
    const value = this.state.value
    const isValid = this.state.isValid

    const fieldData = {
      id,
      value,
      type,
      required,
      formId,
      valid: isValid,
      message
    }

    return (
      <div className={classes}>
        <FieldLabel
          name={id}
          label={label}
          required={required}
          requireDefault={requireDefault}
          hidden={hideLabel}
        />

        <div className={`${type}-field__wrapper`}>
          <FieldDescription
            name={id}
            description={description}
            hidden={hideDescription}
          />

          {!isValid && <FieldErrorMessage name={id} message={message} />}

          <FieldSelect
            {...this.props}
            value={value}
            handleChange={this._handleChange}
            fieldData={fieldData}
          />

          {type === 'date' && <div className="date-field__icon" />}
        </div>
      </div>
    )
  }
}

export default FieldSelectWrap