Form Validation with Semantic-UI-React
Asked Answered
S

9

20

I am using the official Semantic UI React components to create a web application. I have a form on my sign up page, which contains an email field, a password field, and a confirm password field.

import {Component} from 'react';
import {Button, Form, Message} from 'semantic-ui-react';
import {signUp} from '../../actions/auth';

class SignUp extends Component {
    constructor(props) {
        super(props);
        this.handleSubmit = this.handleSubmit.bind(this);
    }
    handleSubmit(e, {formData}) {
        e.preventDefault();

        //
        // Potentially need to manually validate fields here?
        //

        // Send a POST request to the server with the formData
        this.props.dispatch(signUp(formData)).then(({isAuthenticated}) => {
            if (isAuthenticated) {
                // Redirect to the home page if the user is authenticated
                this.props.router.push('/');
            }
        }
    }
    render() {
        const {err} = this.props;

        return (
            <Form onSubmit={this.handleSubmit} error={Boolean(err)}>
                <Form.Input label="Email" name="email" type="text"/>
                <Form.Input label="Password" name="password" type="password"/>
                <Form.Input label="Confirm Password" name="confirmPassword" type="password"/>

                {err &&
                    <Message header="Error" content={err.message} error/>
                }

                <Button size="huge" type="submit" primary>Sign Up</Button>
            </Form>
        );
    }
}

Now, I am used to the regular Semantic UI library, which has a Form Validation addon. Usually, I would define the rules like so in a separate JavaScript file

$('.ui.form').form({
    fields: {
        email: {
            identifier: 'email',
            rules: [{
                type: 'empty',
                prompt: 'Please enter your email address'
            }, {
                type: 'regExp',
                value: "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?",
                prompt: 'Please enter a valid email address'
            }]
        },
        password: {
            identifier: 'password',
            rules: [{
                type: 'empty',
                prompt: 'Please enter your password'
            }, {
                type: 'minLength[8]',
                prompt: 'Your password must be at least {ruleValue} characters'
            }]
        },
        confirmPassword: {
            identifier: 'confirmPassword',
            rules: [{
                type: 'match[password]',
                prompt: 'The password you provided does not match'
            }]
        }
    }
});

Is there a similar method using the Semantic UI React components for validating the form? I've searched through the documentation without any success, and there doesn't seem to be examples of validation using this Semantic UI React library.

Do I instead need to validate each field by hand in the handleSubmit function? What is the best way to fix this problem? Thanks for the help!

Staciastacie answered 18/1, 2017 at 16:15 Comment(2)
Through the course of learning React I stumbled upon forms, which I felt react might be having as part of its core features. But unfortunately it doesn't. It has become a reason for me not to like react much compared to Angular.Calyptrogen
@PankajPrakash, this question is a few years old, and there are many great options available for form validation now. I'm now using formik and yup, which does a great job of validating formsStaciastacie
L
7

We even have a better option, though not provided by semantic-ui-react -> Formik + yup
Formik: helps in managing form state Yup: helps in the validation of that state

I have the below component which is basically an edit form created using semantic-ui-react.

import React, { Component } from "react";
import { Button, Form, Modal, Message, Divider } from "semantic-ui-react";
import { Formik } from "formik";
import * as yup from "yup";


class EditAboutGrid extends Component {

  render() {
    const {
      userBasic,
      editBasicModal,
      closeModal
    } = this.props;

    return (
      <Formik
        initialValues={{
          firstName: userBasic.firstName,
          lastName: userBasic.lastName,
          bio: userBasic.bio,
        }}
        validationSchema={yup.object().shape({
          firstName: yup
            .string()
            .required("Name cannot be empty"),
          lastName: yup
            .string()
            .required("Name cannot be empty"),
          bio: yup
            .string()
            .max(1000, "Maximum characters exceed 1000")
            .nullable()
        })}
        onSubmit={(values, actions) => {
          //do your stuff here like api calls
        }}
        render={({
          values,
          errors,
          handleChange,
          handleSubmit,
          isSubmitting,
          dirty,
          setFieldValue
        }) => (
          <Modal open={editBasicModal} size="small">
            <Modal.Header>Your basic details</Modal.Header>
            <Modal.Content scrolling>
              {errors.firstName && <Message error content={errors.firstName} />}
              {errors.lastName && <Message error content={errors.lastName} />}
              {errors.bio && <Message error content={errors.bio} />}

              <Form loading={isSubmitting}>
                <Form.Group inline widths="equal">
                  <Form.Input
                    required
                    label="First Name"
                    fluid
                    type="text"
                    name="firstName"
                    value={values.firstName}
                    onChange={handleChange}
                    error={errors.firstName !== undefined}
                  />
                  <Form.Input
                    required
                    label="Last Name"
                    fluid
                    type="text"
                    name="lastName"
                    value={values.lastName}
                    onChange={handleChange}
                    error={errors.lastName !== undefined}
                  />
                </Form.Group>
                <Form.TextArea
                  label="Bio"
                  type="text"
                  name="bio"
                  value={values.bio}
                  onChange={handleChange}
                  rows={3}
                  error={errors.bio !== undefined}
                />
              </Form>
            </Modal.Content>
            <Modal.Actions open={true}>
              <Button
                onClick={() => (dirty ? closeModal(true) : closeModal(false))}>
                Cancel
              </Button>
              <Button
                primary
                type="submit"
                onClick={handleSubmit}
                loading={isSubmitting}
                disabled={isSubmitting || !isEmpty(errors) || !dirty}>
                Update
              </Button>
            </Modal.Actions>
          </Modal>
        )}
      />
    );
  }
}

And this form is called using:

  <EditAboutGrid
    editBasicModal={this.state.editBasicModal}
    userBasic={this.state.user.basic}
    closeModal={this.closeModal}
  />

initialValues is the place where the things start. Here you pass on the initial/default values to the inputs of your form. values(in form) will pick data value from this default.

validationSchema is the place where all validation happens using yup

onSubmit would be called on the form submission.

Handling form using Formik + yup is very easy. I am loving it.

Loki answered 3/5, 2019 at 14:9 Comment(2)
Just a note for people using this code. You will require a isEmpty() function as follows: ``` function isEmpty(arg){ return ( arg == null || // Check for null or undefined arg.length === 0 || // Check for empty String (Bonus check for empty Array) (typeof arg === 'object' && Object.keys(arg).length === 0) // Check for empty Object or Array ); } ``` The Formik render() function has been deprecated as of the most recent version at 2020-12-30.Henriques
Or you can import it from lodashLoki
U
9

For the most part, you have to validate forms by hand. However, RSUI includes a couple of tools to make things a bit easier, in particular the error prop on <Form> and <Form.Input>

Here's an example of a form I put together recently. It could use a bit of refactoring, but it basically works by tying each input to state with an onChange() function, and passing a callback to the submit function which controls the visibility of the loading screen and the "Success. Thank you for submitting" portion of the form.

export default class MeetingFormModal extends Component {

  constructor(props) {
    super(props)

    this.state = {
      firstName: '',
      lastName: '',
      email: '',
      location: '',
      firstNameError: false,
      lastNameError: false,
      emailError: false,
      locationError: false,
      formError: false,
      errorMessage: 'Please complete all required fields.',
      complete: false,
      modalOpen: false
    }

    this.submitMeetingForm = this.submitMeetingForm.bind(this);
    this.successCallback = this.successCallback.bind(this);
  }


  successCallback() {
    this.setState({
      complete: true
    })
    setTimeout( () => {this.setState({modalOpen: false})}, 5000);
    this.props.hideLoading();
  }

  handleClose = () => this.setState({ modalOpen: false })
  handleOpen = () => this.setState({ modalOpen: true })

  submitMeetingForm() {

    let error = false;

    if (this.state.studentFirstName === '') {
      this.setState({firstNameError: true})
      error = true
    } else {
      this.setState({firstNameError: false})
      error = false
    }
    if (this.state.studentLastName === '') {
      this.setState({lastNameError: true})
      error = true
    } else {
      this.setState({lastNameError: false})
      error = false
    }
    if (this.state.email === '') {
      this.setState({emailError: true})
      error = true
    } else {
      this.setState({emailError: false})
      error = false
    }
    if (this.state.location === '') {
      this.setState({locationError: true})
      error = true
    } else {
      this.setState({locationError: false})
      error = false
    }

    if (error) {
      this.setState({formError: true})
      return
    } else {
      this.setState({formError: false})
    }


    let meeting = {
      first_name: this.state.firstName,
      last_name: this.state.lastName,
      email: this.state.email,
      location: this.state.location,

    this.props.createMeeting(meeting, this.successCallback)
    this.props.showLoading();
  }

  capitalize(string) {
    return string.charAt(0).toUpperCase() + string.slice(1);
  }

  render() {
    return(
      <Modal
        trigger={<Button onClick={this.handleOpen} basic color='blue'>Schedule Now</Button>}
        open={this.state.modalOpen}
        onClose={this.handleClose}
        closeIcon={true}
      >
        <Modal.Header>Schedule Your Interview</Modal.Header>
        <Modal.Content>
          {!this.state.complete ?
          <Modal.Description>
            <Form error={this.state.formError}>
              <Form.Group widths='equal'>
                <Form.Field>
                  <Form.Input required={true} onChange={(e) => this.setState({firstName: e.target.value})} label='First Name' placeholder="First Name..." error={this.state.firstNameError}/>
                </Form.Field>
                <Form.Field>
                  <Form.Input required={true} onChange={(e) => this.setState({lastName: e.target.value})} label='Last Name' placeholder="Last Name..." error={this.state.lastNameError}/>
                </Form.Field>
              </Form.Group>
              <Form.Field >
                <Form.Input required={true} onChange={(e) => this.setState({email: e.target.value})} label='Email' placeholder="Email..." error={this.state.emailError}/>
              </Form.Field>
              <Form.Field>
                <Form.Input required={true} onChange={(e) => this.setState({location: e.target.value})} label='Location' placeholder='City, State/Province, Country...' error={this.state.locationError}/>
              </Form.Field>
            </Form>
          </Modal.Description>
          : 
            <div className='modal-complete'>
              <Image src='/images/check.png' />
              <p>Thanks for scheduling a meeting, {this.capitalize(this.state.name)}. We've received your information and we'll be in touch shortly.</p>
            </div>
          }
        </Modal.Content>
        {!this.state.complete ?
        <Modal.Actions>
          <Button color='red' onClick={this.handleClose}>Close</Button>
          <Button positive icon='checkmark' labelPosition='right' content="Submit" onClick={this.submitMeetingForm} />
        </Modal.Actions>
        : null }
      </Modal>
    )
  }
}

Hope that helps!

Ulyssesumayyad answered 19/9, 2017 at 2:42 Comment(2)
let meeting = { ... is not closedGrafting
Now It seems that I can avoid Formik or Redux-forms to manage any form "ephimeral" state. Nice sample, thanks @UlyssesumayyadGrafting
L
7

We even have a better option, though not provided by semantic-ui-react -> Formik + yup
Formik: helps in managing form state Yup: helps in the validation of that state

I have the below component which is basically an edit form created using semantic-ui-react.

import React, { Component } from "react";
import { Button, Form, Modal, Message, Divider } from "semantic-ui-react";
import { Formik } from "formik";
import * as yup from "yup";


class EditAboutGrid extends Component {

  render() {
    const {
      userBasic,
      editBasicModal,
      closeModal
    } = this.props;

    return (
      <Formik
        initialValues={{
          firstName: userBasic.firstName,
          lastName: userBasic.lastName,
          bio: userBasic.bio,
        }}
        validationSchema={yup.object().shape({
          firstName: yup
            .string()
            .required("Name cannot be empty"),
          lastName: yup
            .string()
            .required("Name cannot be empty"),
          bio: yup
            .string()
            .max(1000, "Maximum characters exceed 1000")
            .nullable()
        })}
        onSubmit={(values, actions) => {
          //do your stuff here like api calls
        }}
        render={({
          values,
          errors,
          handleChange,
          handleSubmit,
          isSubmitting,
          dirty,
          setFieldValue
        }) => (
          <Modal open={editBasicModal} size="small">
            <Modal.Header>Your basic details</Modal.Header>
            <Modal.Content scrolling>
              {errors.firstName && <Message error content={errors.firstName} />}
              {errors.lastName && <Message error content={errors.lastName} />}
              {errors.bio && <Message error content={errors.bio} />}

              <Form loading={isSubmitting}>
                <Form.Group inline widths="equal">
                  <Form.Input
                    required
                    label="First Name"
                    fluid
                    type="text"
                    name="firstName"
                    value={values.firstName}
                    onChange={handleChange}
                    error={errors.firstName !== undefined}
                  />
                  <Form.Input
                    required
                    label="Last Name"
                    fluid
                    type="text"
                    name="lastName"
                    value={values.lastName}
                    onChange={handleChange}
                    error={errors.lastName !== undefined}
                  />
                </Form.Group>
                <Form.TextArea
                  label="Bio"
                  type="text"
                  name="bio"
                  value={values.bio}
                  onChange={handleChange}
                  rows={3}
                  error={errors.bio !== undefined}
                />
              </Form>
            </Modal.Content>
            <Modal.Actions open={true}>
              <Button
                onClick={() => (dirty ? closeModal(true) : closeModal(false))}>
                Cancel
              </Button>
              <Button
                primary
                type="submit"
                onClick={handleSubmit}
                loading={isSubmitting}
                disabled={isSubmitting || !isEmpty(errors) || !dirty}>
                Update
              </Button>
            </Modal.Actions>
          </Modal>
        )}
      />
    );
  }
}

And this form is called using:

  <EditAboutGrid
    editBasicModal={this.state.editBasicModal}
    userBasic={this.state.user.basic}
    closeModal={this.closeModal}
  />

initialValues is the place where the things start. Here you pass on the initial/default values to the inputs of your form. values(in form) will pick data value from this default.

validationSchema is the place where all validation happens using yup

onSubmit would be called on the form submission.

Handling form using Formik + yup is very easy. I am loving it.

Loki answered 3/5, 2019 at 14:9 Comment(2)
Just a note for people using this code. You will require a isEmpty() function as follows: ``` function isEmpty(arg){ return ( arg == null || // Check for null or undefined arg.length === 0 || // Check for empty String (Bonus check for empty Array) (typeof arg === 'object' && Object.keys(arg).length === 0) // Check for empty Object or Array ); } ``` The Formik render() function has been deprecated as of the most recent version at 2020-12-30.Henriques
Or you can import it from lodashLoki
L
3

I know this question is several years old, but this was something I struggled with doing while using the (relatively) newer React functional components and Hooks API. In my case, I just wanted to validate that the user input was a valid email address. I ended up getting the behavior I was looking for with the code below.

import React, {useState} from 'react';
import { Form } from 'semantic-ui-react'
import EmailValidator from 'email-validator';


function MyFormComponentExample() {
    const [emailInput, setEmail] = useState("");
    const [validEmail, validateEmail] = useState(true);

    return (
    <Form>
        <Form.Input
            icon='envelope'
            iconPosition='left'
            label='Email'
            placeholder='Email Address'
            required
            value={emailInput}
            onChange={e => {
                setEmail(e.target.value);                               
                validateEmail(EmailValidator.validate(e.target.value));
            }}
            error={validEmail ? false : {
                content: 'Please enter a valid email address.',
                pointing: 'below'
            }}
        />
    </Form>
    );
}

export default MyFormComponentExample;

Once I figured it out, I thought this structure was simple enough, but if anyone has feedback about a better pattern or approach to perform this I would love to hear it!

Lunetta answered 28/10, 2020 at 15:2 Comment(0)
U
1

Do I instead need to validate each field by hand in the handleSubmit function?

Sad, but true. SUIR doesn't have form validation at now. However, you can use HOC to work with forms like redux-form.

Unjust answered 7/2, 2017 at 9:28 Comment(0)
A
1

You can use plugin for validation. Plugin name : formsy-semantic-ui-react

Acceptation answered 28/9, 2018 at 5:18 Comment(1)
This is what I ended up doing. But it's unfortunate that semantic-ui-react does not have this capability when semantic-ui does. formsy-semantic-ui-react is a third party package, so I'm concerned that it will not get the same amount of support that an official package would getStaciastacie
R
0

The code below essentially sets state to each components name and associated value. (I.E, state might look like {marketSide:buy, price:50, quantity:9} I also stuff the forms error info into state as well, taking advantage of yup's default behavior to not validate fields that are not mentioned by the validation schema.

Important points:

1) The call to schema.validate(someObjectToValidate, yupProperties), (where someObjectToValidate is just this.state), should pass in {abortEarly:false} as the properties object, to override yups default behavior to stop validation once a single error has been encountered, as our form's message component displays all errors to the user.

2)If yup validation fails, yup throws an exception, so we catch the exception and pick off the error info we are interested in, and update our state with this error.

3)We must use the 'callback form' of this.setState(...), since setState is asynchronous, so that validation of the state object only happens after state has been updated.

4)if yup validation succeeds, we clear our errors and errorPath arrays.

5)This is a quick solution, I am checking the errorPaths array several times on each render. It would be better to store the errorPath and error info on in a json object keyed by the component's name, instead of in two arrays, to improve the current solution's (2 arrays * N fields * N potential errors = O(2n^2) performance.

6)Disregard the long style '<Form.Field control={Radio}>' notation, you could use the shorter <Input>, <Button>, <Radio>, etc style. This syntax has nothing to do with the validation. Also ignore the divs.

import React, { Component } from 'react'
import { Button, Checkbox, Form, Input, Radio, Select, Message,  Label } from 'semantic-ui-react'
import * as yup from 'yup';


const options = [
  { text: 'buy', value: 'buy' },
  { text: 'sell', value: 'sell' },
]

class OrderEntryV3 extends Component {
  state = {
      errorPaths:[],
      errors:[]
  }

  constructor(props){
      super(props);
  }


  schema = yup.object().shape({
    quantity: yup.number().required().positive().integer(),
    price:  yup.number().required().positive(),
    marketSide: yup.string().required(),
    orderType : yup.string().required()
  });



  handleChange = (e, component) => {
      this.setState({[component.name]:component.value}, ()=>this.schema.validate(this.state, {abortEarly:false})
         .then(valid=>this.setState({errorPaths:[], errors:[]})) //called if the entire form is valid
         .catch(err=>this.setState({errors:err.errors, errorPaths: err.inner.map(i=>i.path) }))) //called if any field is invalid
    };


  render() {

    return (
<div id="oeform-content">
    <div id="oeform-left">
      <Form>
          <Form.Field  error={this.state.errorPaths.includes('marketSide')} name="marketSide" control={Select} label='Market side' options={options} placeholder='market side' onChange={this.handleChange}/>
          <Form.Field  error={this.state.errorPaths.includes('quantity')} type='number' name="quantity" control={Input} label='Quantity' placeholder='quantity' onChange={this.handleChange}/>

          <Form.Group>
              <label><b>Order type</b></label>  
        <Form.Field error={this.state.errorPaths.includes('orderType')} >
          <Radio
            label='market'
            name='orderType'
            value='market'
            checked={this.state.orderType === 'market'}
            onChange={this.handleChange}
          />
        </Form.Field>
        <Form.Field error={this.state.errorPaths.includes('orderType')}>
          <Radio
            label='limit'
            name='orderType'
            value='limit'
            checked={this.state.orderType === 'limit'}
            onChange={this.handleChange}
          />
        </Form.Field>
    </Form.Group>
        <Form.Field error={this.state.errorPaths.includes('price')} name='price' control={Input} type='number' label='Price' placeholder='price' onChange={this.handleChange}/>
        <Form.Field control={Button} disabled={!!this.state.errors.length}>Submit</Form.Field>
        <Message visible={!!this.state.errors.length} warning
        header='Please correct the following issues: '
        list={this.state.errors}/>
      </Form>
    </div>
    <div id="oeform-right">
        <p>{JSON.stringify(this.state)}</p>
    </div>
</div>
    )
  }
}

export default OrderEntryV3
Rayburn answered 18/10, 2018 at 1:6 Comment(0)
G
0

I understand this is rather couple of years old and is not a complete solution for the question asked but anyone looking today might find this useful as I was looking for the same and ran into this thread. Though semantic-ui-react does not have a form validation that I can find, but it has a nice way to show form validation errors. You still have to do the validation on your own. Refer to semantic-ui-react forms documentation page for an example of how it shows validation errors. It also has a feature to show success message. I have created an example of how you can control the error message based on the state. In this example, you can use the "Show Gender Error" checkbox to toggle the error message on the Gender input.

Geanticlinal answered 4/6, 2020 at 6:0 Comment(1)
Where does Semantic UI React forms documentation say anything about validation?Lyall
H
-1

For validate form in semantic-ui you can simply achieve without any plugin

<Form.Field
      id='form-input-control-project-name'
      control={Input}
      label='Project Name'
      placeholder='Project name'
      onChange={this.handleChange}
      error={this.state.projectName}
    />

this.state.projectName In This variable you have to store false/error message

If the value is false not display error otherwise display error

Harlamert answered 31/7, 2020 at 7:21 Comment(0)
L
-1

You can use Formik to do validation.

And I have created a library to bind Formik and Semantic UI React.

https://github.com/JT501/formik-semantic-ui-react

A quick example: Codesandbox

Luftwaffe answered 2/11, 2020 at 11:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.