0
\$\begingroup\$

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
200_success
145k22 gold badges190 silver badges478 bronze badges
asked Aug 13, 2018 at 14:00
\$\endgroup\$
2
  • \$\begingroup\$ How would they be used? Is their usage similar as well? \$\endgroup\$ Commented Aug 13, 2018 at 15:33
  • \$\begingroup\$ I'm using these to build forms. The first is the primary component called when including text fields, and the second when including select fields. So, their usage is similar, being children of my form module, but different in that they're used to manage different types of fields. Hopefully, that makes sense. \$\endgroup\$ Commented Aug 13, 2018 at 15:48

1 Answer 1

2
\$\begingroup\$

Combining them to reduce duplicate code is not a good approach. Prefer small composable components over large Swiss army knife components. Think single responsibility principle.

If your component takes a lot of properties, it may be a good candidate to make smaller. If you are testing for type, you may want to create multiple controls.

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

In your example, I recommend generating a generic wrapper component with a nested child.

Create wrapper component:

class LabelWrapper extends Component {
 ...
 render() {
 const {text, icon, error, children} = this.props;
 const ErrorMessage = this.getError(error);
 const Icon = this.getIcon(icon);
 return (
 <div className="label-wrapper">
 <label>{text}</label>
 <ErrorMessage/>
 {children}
 <Icon>
 </div>
 );
 }
 getIcon(icon) {
 return icon && <div className={icon} />}
 }
}

Use Wrapper in form passing in child component:

<LabelWrapper
 text='Full Name'
 error={error.fullName}>
 <TextInput
 id='fullName'
 value={form.firstName}
 handleChange={handleChange.firstName}/>
</LabelWrapper>
<LabelWrapper
 text='Choose Date'
 icon='date-field__icon'
 error={error.date}>
 <FieldSelect
 value={form.date}
 handleChange={handleChange.chooseDate}
 fieldData={fieldData} />
</LabelWrapper>

You can also compose a more specific control. This will prevent your form from getting too large.

class ChooseDate extends Component {
 ...
 render() {
 const {fieldData, handleChange, value, error} = this.props;
 return (
 <LabelWrapper
 text='Choose Date'
 icon='date-field__icon"'
 error={error}
 <FieldSelect
 value={value}
 handleChange={handleChange}
 fieldData={fieldData}/>
 </LabelWrapper>
 );
 }
}

A few other notes:

Resources:

answered Sep 15, 2018 at 12:25
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.