Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React/React-Hooks: Need to run a function onLoad within React Hooks component

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

like image 265
Josh Avatar asked Jun 18 '19 14:06

Josh


People also ask

How do you call a loading function with React useEffect only once?

To run the useEffect hook callback only once when the component mounts, we just have to pass in an empty array into the 2nd argument of useEffect hook. We just pass in an empty array into useEffect and the callback would only run once.

How do you call a function when a page loads in React?

The useEffect runs by default after every render of the component. When placing useEffect in our component we tell React that we want to run the callback as an effect. React will run the effect after rendering and after performing the DOM updates.

Which is neither a React function component or a custom React hook function React Hooks rules of Hooks?

React Hook 'useState' is called in function that is neither a React function component nor a custom React Hook function.

Can I use a React hook inside a function?

Don't call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders.


Video Answer


1 Answers

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.

like image 69
Kobe Avatar answered Sep 20 '22 05:09

Kobe