0

I have a previous form validation of a controlled form, and I'm trying to convert the class component to a function component using hooks. But I have problems implementing the original onBlur and onChange methods. My original code:

import React, {Component} from 'react';
import { Label, Button, Col, Row, FormFeedback, Input, Form } from 'reactstrap';

function SignUp (){
    return(
        <div className="container">
            <div className="row" style={{'padding': '30px 12px 60px 12px'}}>
            <div className="col col-md-8">
                <h4>Inscríbete y crea tu galería de pesonajes!</h4>
            </div>
        </div>
        <div className="row row-content">
            <div className="col col-md-8">
                {<SignUpForm />}
            </div>
        </div>
    </div>
 );
}
class SignUpForm extends Component {

  constructor(props){
      super(props);
  this.state= {
      firstName: '',
      lastName: '',
      email: '',
      username: '',
      pass: '',
      touched:{
        firstName: false,
        lastName: false,
        email: false,
        pass: false,
      }
  }

  this.handleSubmit = this.handleSubmit.bind(this);
  this.handleInput = this.handleInput.bind(this);
  this.handleBlur = this.handleBlur.bind(this);
  }

  handleInput (e) {
    const target = e.target;
    const value = target.type == 'checkbox'? target.checkbox: target.value
    const name = target.name;

this.setState({
    [name]: value,
})
this.setState({username: this.state.email})
  }

  handleBlur = (field) => (e) => {
      this.setState({
          touched : { ...this.state.touched, [field]: true}
      })
  }

  validate (firstName, lastName, email, pass){
  const validEmail = (val) => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(val);
  const errors = {
    firstName: '',
    lastName: '',
    email: '',
    password: ''
  }

    if (this.state.touched.firstName && firstName.length < 2)
        errors.firstName = 'Tu nombre debe tener más de 3 caracteres'

    else if (this.state.touched.firstName && firstName.length > 15)
        errors.firstName = 'Tu nombre debe tener menos de 15 caracteres'

    if (this.state.touched.lastName && lastName.length < 2)
        errors.lastName = 'Tu nombre debe tener más de 3 caracteres'

    else if (this.state.touched.lastName && lastName.length > 15)
        errors.lastName = 'Tu nombre debe tener menos de 15 caracteres'

    if (this.state.touched.email && !validEmail(email))
        errors.email = 'Email No valido'

    else if (this.state.touched.pass && pass.length < 4)
        errors.pass = 'Tu password debe tener más de 3 caracteres'

    return errors
}

 handleSubmit(e) {
    //some fetch operation
  }

render(){
    //const {firstName, lastName, email, pass} = this.state
    const errors = this.validate(this.state.firstName, this.state.lastName, this.state.email, this.state.pass)
    return(
      <Form onSubmit={this.handleSubmit}>
        <Row className="form-group">
          <Label htmlFor="firstname" md={2}><strong>Tu Nombre</strong></Label>
          <Col md={4}>
                <Input type="text" id="firstName" name="firstName"
                  placeholder="Juanito"
                  value={this.state.firstName}
                  className="form-control"
                  valid={errors.firstName === ''}
                  invalid={errors.firstName !== ''}
                  onBlur={this.handleBlur('firstName')}
                  onChange={this.handleInput} />
                <FormFeedback>{errors.firstName}</FormFeedback>
            </Col>
            <Label htmlFor="lastname" md={2}><strong>Tu Apellido</strong></Label>
            <Col md={4}>
              <Input type="text" id="lastName" name="lastName"
                  placeholder="Pérez"
                  value={this.state.lastName}
                  className="form-control"
                  valid={errors.lastName === ''}
                  invalid={errors.lastName !== ''}
                  onBlur={this.handleBlur('lastName')}
                  onChange={this.handleInput} />
                <FormFeedback>{errors.lastName}</FormFeedback>
          </Col>
          </Row>
          <Row className="form-group">
          <Label htmlFor="email" md={2}><strong>Email(*)</strong></Label>
          <Col md={4}>
            <Input type="email" id="email" name="email"
                placeholder="[email protected]"
                className="form-control"
                valid={errors.email === ''}
                invalid={errors.email !== ''}
                onBlur={this.handleBlur('email')}
                onChange={this.handleInput}/>
            <FormFeedback>{errors.email}</FormFeedback>
          </Col>
          <Label htmlFor="password" md={2}><strong>Contraseña</strong></Label>
            <Col md={4}>
                <Input type="password" id="pass" name="password"
                    className="form-control"
                    valid={errors.pass === ''}
                    invalid={errors.pass !== ''}
                    onBlur={this.handleBlur('password')}
                    onChange={this.handleInput} />
                <FormFeedback>{errors.pass}</FormFeedback>
            </Col>
        </Row>
        <Row className="form-group">
          <Col md={{size:2, offset:10}}>
              <Button type="submit" value="submit" name="submit" color="primary">
                  Inscribirse
              </Button>
          </Col>
        </Row>
      </Form>
    )
 }
}

export default SignUp;

And Here I tried to implement the same:

import React, { useState } from 'react';
import { Label, Button, Col, Row, FormFeedback, Input, Form } from 'reactstrap';


function SignUp (){
    return(
        <div className="container">
            <div className="row" style={{'padding': '30px 12px 60px 12px'}}>
                <div className="col col-md-8">
                    <h4>Inscríbete y crea tu galería de pesonajes!</h4>
                </div>
            </div>
            <div className="row row-content">
                <div className="col col-md-8">
                    {<SignUpForm />}
                </div>
            </div>
        </div>
    );
}

function SignUpForm() {

    const [formState, setFormState] = useState({firstName: '', lastName: '', email: '', pass: ''})
    const [username, setUsername] = useState('')
    const [touched, setTouched] = useState({firstName: false, lastName: false, email: false, pass: false})

    function handleInput (e) {
        const target = e.target;
        const value = target.type == 'checkbox'? target.checkbox: target.value
        const name = target.name;
        setFormState({ [name]: value })
    }

    function handleBlur(e) { 
        console.log(e.target.name)
        setTouched({...touched, [e.target.name]: true})

    }

    function validate(firstName, lastName, email, pass) {
        const validEmail = (val) => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(val);
        const errors = {
            firstName: '',
            lastName: '',
            email: '',
            password: ''
        }

        if (touched.firstName && firstName.length <= 2)
            errors.firstName = 'Tu nombre debe tener más de 2 caracteres'

        else if (touched.firstName && firstName.length > 15)
            errors.firstName = 'Tu nombre debe tener menos de 15 caracteres'

        if (touched.lastName && lastName.length < 2)
            errors.lastName = 'Tu apellido debe tener más de 3 caracteres'

        else if (touched.lastName && lastName.length > 15)
            errors.lastName = 'Tu apellido debe tener menos de 15 caracteres'

        if (touched.email && !validEmail(email))
            errors.email = 'Email No valido'

        else if (touched.pass && pass.length < 4)
            errors.pass = 'Tu password debe tener más de 3 caracteres'
        return errors
    }

    function handleSubmit(e) {
        //some fetch operation
    }

    const errors = validate(formState.firstName, formState.lastName, formState.email, formState.pass)
    return(
        <Form onSubmit={ () => handleSubmit}>
            <Row className="form-group">
                <Label htmlFor="firstname" md={2}><strong>Tu Nombre</strong></Label>
                <Col md={4}>
                    <Input type="text" id="firstName" name="firstName"
                        placeholder="Juanito"
                        value={formState.firstName}
                        className="form-control"
                        valid={errors.firstName === ''}
                        invalid={errors.firstName !== ''}
                        onBlur={ (e) => handleBlur(e) }
                        onChange={ (e) => handleInput(e) } />
                    <FormFeedback>{errors.firstName}</FormFeedback>
                </Col>
                <Label htmlFor="lastname" md={2}><strong>Tu Apellido</strong></Label>
                <Col md={4}>
                    <Input type="text" id="lastName" name="lastName"
                        placeholder="Pérez"
                        value={formState.lastName}
                        className="form-control"
                        valid={errors.lastName === ''}
                        invalid={errors.lastName !== ''}
                        onBlur={ (e) => handleBlur(e)}
                        onChange={(e) => handleInput(e)} />
                    <FormFeedback>{errors.lastName}</FormFeedback>
                </Col>
                </Row>
                <Row className="form-group">
                <Label htmlFor="email" md={2}><strong>Email(*)</strong></Label>
                <Col md={4}>
                <Input type="email" id="email" name="email"
                    placeholder="[email protected]"
                    value={formState.email}
                    className="form-control"
                    valid={errors.email === ''}
                    invalid={errors.email !== ''}
                    onBlur={ (e) => handleBlur(e)}
                    onChange={(e) => handleInput(e)} />
                <FormFeedback>{errors.email}</FormFeedback>
                </Col>
                <Label htmlFor="password" md={2}><strong>Contraseña</strong></Label>
                <Col md={4}>
                    <Input type="password" id="pass" name="password"
                        value={formState.pass}
                        className="form-control"
                        valid={errors.pass === ''}
                        invalid={errors.pass !== ''}
                        onBlur={ (e) => handleBlur(e)}
                        onChange={(e) => handleInput(e)} />
                    <FormFeedback>{errors.pass}</FormFeedback>
                </Col>
            </Row>
            <Row className="form-group">
                <Col md={{size:2, offset:10}}>
                    <Button type="submit" value="submit" name="submit" color="primary">
                        Inscribirse
                    </Button>
                </Col>
            </Row>
        </Form>
    )
}

export default SignUp;

When I beggin input values in the second field (lastname) there's and error saying 'firstname' is undefined (cannot read length property of undefined). I think the problem is with the fact that setFirstName doesn't get to set the value before the validation function, but after hourse rearranging the code I can't find a way to fix it

4
  • setFormState({ ...formState, [name]: value })? Commented Jan 10, 2020 at 22:33
  • @VasylButov There I'm trying to provide previous state and set the new value of the correspondent property Commented Jan 10, 2020 at 22:49
  • I mean, your setFormState doesn't spread object, which may cause unpredictable errors. Have you tried it my way? Commented Jan 10, 2020 at 22:55
  • Thanks man! I was so dizzy I hadn't notice that detail! Commented Jan 11, 2020 at 1:47

1 Answer 1

1

I was struggling with this as well and would like to see this marked as answered because it helped me.

As @VasylButov pointed out, setFormState([name]: value) is over-writing the formState object and setting it to value. Use the spread operator ...formState first, then modifying the property you want setFormState({...formState, [name]: value}).

As a new contributor to SO, I'm not 100% sure what the etiquette is on posting an answer that was already answered in a comment so please forgive me if this isn't correct.

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

Comments

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.