10

I have a parent component with a function that runs a validation on an email input field and passes that function as a prop down to the child component that actually contains the input field needing validation. Validations are working, the only issue I have now is that the validation doesn't run on load so even when the input field is pre populated with a user email, the validation's default state is set as failing so that a user will have to click into the input field and click out in order to trigger the onBlur function and thus the validation. I'm not sure how to run a function on load using react hooks as I'm still fairly new to using them.

Parent Component

import React, { Component, Fragment } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import styled from 'styled-components'
import { Row, Col } from 'antd'
import * as actions from '../../actions'
import { tableColumnProps } from '../../propTypes'
import Section from '../../components/layout/Section'
import CustomerDetails from '../../components/orders/CustomerDetails'
import AccountDetails from '../../components/orders/AccountDetails'
import ExistingServices from '../../components/orders/ExistingServices'
import OfferBundlesFilters from '../../components/orders/OfferBundlesFilters'
import OfferBundlesTable from '../../components/orders/OfferBundlesTable'
import OffersHeader from '../../components/orders/OffersHeader'

function validateEmail(value) {
  const errors = { hasError: false }
  if (value === '') {
    errors.email = 'Email address or Email Opt Out is required'
    errors.hasError = true
    return errors
  }

  if (!/\S+@\S+\.\S+/.test(value)) {
    errors.email = 'Email address is invalid'
    errors.hasError = true
    return errors
  }
  return errors
}

export class OffersPage extends Component {
  constructor(props) {
    super(props)

    this.state = {
      customerEmail: {},
      disableEmailValidation: false,
      emailValidation: {
        isValid: false,
        validationError: ''
      }
    }
  }

  componentDidMount() {
    this.props.getOffers()
  }

  toggleDisableInput = value => {
    this.setState({ disableEmailValidation: value })
  }

  removeEmailValidationError = () => {
    this.setState({
      emailValidation: {
        isValid: true,
        validationError: ''
      }
    })
  }

  checkIfCustomerEmailIsValid = inputValue => {
    const validationResult = validateEmail(inputValue)
    if (validationResult.hasError === true) {
      this.setState({
        emailValidation: {
          isValid: false,
          validationError: validationResult.email
        }
      })
    } else {
      this.removeEmailValidationError()
    }
  }

  getEmailValue = email => {
    if (email.hasError) {
      this.setState({ customerEmail: email })
    }
  }

  render() {
    const {
      customer,
      offers,
      services,
      // selectOffer,
      selectedOffer = {},
      offerColumns,
      filters,
      updateFilter,
      updateCustomer
    } = this.props

    return (
      <Fragment>
        <Row gutter={48}>
          <Col span={24}>
            <OffersHeader
              customer={customer}
              onPaidByChange={updateCustomer}
            />
          </Col>
        </Row>
        <SectionRow>
          <div>
            <Section title="Customer">
              <CustomerDetails
                customer={customer}
                getEmailValue={this.getEmailValue}
                checkIfCustomerEmailIsValid={this.checkIfCustomerEmailIsValid}
                emailValidation={this.state.emailValidation}
                disableEmailValidation={this.state.disableEmailValidation}
                toggleDisableInput={this.toggleDisableInput}
                removeEmailValidationError={this.removeEmailValidationError}
              />
            </Section>
            <Section title="Account Information">
              <AccountDetails />
            </Section>
          </div>
          <div>
            <Section title="Existing Services">
              <ExistingServices services={services} />
            </Section>
          </div>
        </SectionRow>
        <Row gutter={48}>
          <Col span={24}>
            <StyledFiltersSection title="Filters">
              <OfferBundlesFilters
                filters={filters}
                onFilterChange={updateFilter}
              />
            </StyledFiltersSection>
          </Col>
        </Row>
        <Row gutter={48}>
          <Col span={24}>
            <Section title="Offers">
              <OfferBundlesTable
                columns={offerColumns}
                bundles={offers}
                viewedOfferIds={[selectedOffer.OfferId]}
                onBundleSelect={this.handleSelectOffer}
              />
            </Section>
          </Col>
        </Row>
      </Fragment>
    )
  }
}

const mapStateToProps = state => ({
  customer: state.customer.details,
  offers: state.offers.all,
  offerColumns: state.offers.tableColumns,
  selectedOffer: state.offers.selectedOffer,
  filters: Object.values(state.offers.filters),
  services: state.offers.services,
  pages: state.navigation
})

const mapDispatchToProps = {
  getOffers: actions.getOffers,
  selectOffer: actions.selectOffer,
  updateFilter: actions.updateOfferFilters,
  updateCustomer: actions.updateCustomer
}

OffersPage.propTypes = {
  customer: PropTypes.object,
  filters: PropTypes.arrayOf(PropTypes.object),
  updateFilter: PropTypes.func.isRequired,
  updateCustomer: PropTypes.func.isRequired,
  getOffers: PropTypes.func.isRequired,
  offers: PropTypes.arrayOf(PropTypes.object),
  offerColumns: tableColumnProps,
  selectOffer: PropTypes.func.isRequired,
  selectedOffer: PropTypes.object,
  services: PropTypes.object,
  location: PropTypes.object,
  history: PropTypes.object
}

OffersPage.defaultProps = {
  customer: null,
  offers: []
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(OffersPage)

Child Component

import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import {
  Col, Row, Icon, Input, Tooltip
} from 'antd'
import Checkbox from '../elements/Checkbox'
import Markup from '../core/Markup'

const CustomerDetails = ({
  customer,
  checkIfCustomerEmailIsValid,
  emailValidation,
  toggleDisableInput,
  disableEmailValidation,
  removeEmailValidationError
}) => {
  const { contact = {}, account = {}, site = {} } = customer || {}
  const [inputValue, setInputValue] = React.useState(contact.email)

  function onBlur(e) {
    checkIfCustomerEmailIsValid(e.target.value)
  }

  function clearInput() {
    setInputValue('')
  }

  function handleInputChange(event) {
    setInputValue(event.target.value)
  }

  return (
    <Container>
        <Row>
          <Col span={10}>
            <h4>
              PRIMARY CONTACT EMAIL &nbsp;
            </h4>
          </Col>
        </Row>
        <Row>
      <Row>
        <Col span={8}>
          <StyledInput
            value={inputValue}
            onChange={handleInputChange}
            disabled={disableEmailValidation}
            onBlur={onBlur}
            isError={!emailValidation.isValid}
          />
          {!emailValidation.isValid && (
            <ErrorDiv>{emailValidation.validationError}</ErrorDiv>
          )}
        </Col>
        <Col span={2} />
        <Col span={8}>
          <StyledCheckbox
            onChange={handleOptOut}
            checked={disableEmailValidation}
            isError={!emailValidation.isValid}
          />{' '}
          EMAIL OPT OUT{' '}
        </Col>
      </Row>
    </Container>
  )
}

CustomerDetails.propTypes = {
  customer: PropTypes.object,
  emailValidation: PropTypes.object,
  checkIfCustomerEmailIsValid: PropTypes.func,
  toggleDisableInput: PropTypes.func
}

CustomerDetails.defaultProps = {
  customer: {}
}


const ErrorCheckbox = ({ isError, ...remainingProps }) => (
  <Checkbox {...remainingProps} />
)

const ErrorInput = ({ isError, ...remainingProps }) => (
  <Input {...remainingProps} />
)

const StyledCheckbox = styled(ErrorCheckbox)`
  &&& {
    background: white;

    input + span {
      width: 35px;
      height: 35px;
      border: 2px solid
        ${({ theme, isError }) =>
    isError ? theme.colors.danger : theme.colors.black};
    }

    input + span:after {
      width: 12.5px;
      height: 20px;
    }

    input:focus + span {
      width: 35px;
      height: 35px;
    }
  }
`

const StyledInput = styled(ErrorInput)`
  max-width: 100%;
  background: white;

  &&& {
    border: 2px solid
      ${({ theme, isError }) =>
    isError ? theme.colors.danger : theme.colors.black};
    border-radius: 0px;
    height: 35px;
  }
`

ErrorInput.propTypes = {
  isError: PropTypes.bool
}

ErrorCheckbox.propTypes = {
  isError: PropTypes.bool
}

const ErrorDiv = styled.div`
  color: #d11314;
`

const ErrorContainer = styled.div`
  span {
    text-align: center;
  }
`

export default CustomerDetails

4
  • 1
    Try using useEffect(() => {}, []) where the function is what you want to execute. This is the same as calling componentDidMount() Commented Jun 18, 2019 at 14:32
  • How would I set it to run on my input field? Commented Jun 18, 2019 at 14:42
  • Can you please narrow down your code, and where in the code the problem lays? Commented Jun 18, 2019 at 14:44
  • Yeah sorry I just tried to clean up the components. I have a method called checkIfCustomerEmailIsValid in my parent component that takes the value from input and runs a validation check. I currently have that set as the function that runs onBlur on my input field. The problem I'm having is that because that function only runs onBlur it sets the input field as having errors even when the field pre populates with an email address from an API. I need to find a way to run validation for that input when everything initially loads Commented Jun 18, 2019 at 14:51

1 Answer 1

21

Try pasting the on blur code into an effect. I see that you're using e.target.value, but you have already explicitly set the value using useState, so use inputValue:

React.useEffect(() => checkIfCustomerEmailIsValid(inputValue), [])

What useEffect does is execute the function provided on the first render and every other render when a variable in the second argument changes. Since we provided an an empty array, it will only ever execute once - when the component initially renders.

Sign up to request clarification or add additional context in comments.

4 Comments

I passed that at the top of my child component right underneath my other hooks and now I'm getting a TypeError react-dom.development.js:18015 Uncaught TypeError: create is not a function
Ok I just passed it in and I have linting rules that are populating it like: React.useEffect(() => checkIfCustomerEmailIsValid(setInputValue), [ checkIfCustomerEmailIsValid, setInputValue ]) every time I save. After I run my app, I'm suddenly getting a 304 error from my API response. Do you think these are related?
Just tried it again, there was an issue with my API but this solved it! Thank you so much for the help!
So I'm having another weird issue now. I have a checkbox that's setup with a function to disable the input field and clear all errors if it's clicked. It was working before the useEffect implementation but now it doesn't work. Any idea on why that is?

Your Answer

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.