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
</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
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.
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.
React Hook 'useState' is called in function that is neither a React function component nor a custom React Hook 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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With