simple-react-validator icon indicating copy to clipboard operation
simple-react-validator copied to clipboard

Validation not working in functional component with hooks

Open sachin8094 opened this issue 5 years ago • 26 comments

I am trying to use is in the functional component using React Hooks but its now showing up the validation message on UI

My React version is below "react": "^16.8.6", "react-dom": "^16.8.6",

Below is my code, Please check if I am missing anything.

import React from "react";
import SimpleReactValidator from "simple-react-validator";

export default function ExampleForm (){
  const [values, setValues] = React.useState({
    title: 'There was a server error the prevented the form from submitting.',
    email:'',
    review:''
  });

  const  handleChange = name => event => {
    setValues({ ...values, [name]: event.target.value });
  };

  const validator = new SimpleReactValidator({
    className: 'text-danger',
    messages: {
      email: 'That is not an email.',
    },
    validators: {
      ip: {
        message: 'The :attribute must be a valid IP address.',
        rule: function(val, params, validator) { 
          return validator.helpers.testRegex(val,/^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|$)){4}$/i) && params.indexOf(val) === -1
        }
      }
    }
  });

  const submitForm= ()=> {
    if (validator.allValid()) {
      alert('You submitted the form and stuff!');
    } else {
      validator.showMessages();
    }
  }

  return (
    <div className="container">
      <div className="form-group">
        <label>Title</label>
        <input className="form-control" value={values.title}  onChange={handleChange("title")} />
 
        {/**********   This is where the magic happens     ***********/}
        {validator.message('title', values.title, 'required|alpha')}
 
      </div>
      <div className="form-group">
        <label>Email</label>
        <input className="form-control" value={values.email} onChange={handleChange("email")} />
 
        {/**********   This is where the magic happens     ***********/}
        {validator.message('email', values.email, 'required|email')}
 
      </div>
      <div className="form-group">
        <label>Review</label>
        <textarea className="form-control" value={values.review} onChange={handleChange("review")} />
 
        {/**********   This is where the magic happens     ***********/}
        {validator.message('review', values.review, 'required|min:20|max:120')}
 
      </div>
      <button className="btn btn-primary" onClick={submitForm.bind(this)}>Save Review</button>
    </div>
  );
}

sachin8094 avatar Jun 07 '19 10:06 sachin8094

@sachin8094 sorry to hear it isn't working for you within a functional component with hooks. I have never used either of those before. If anyone out there has any suggestions on how this works or if it works please chime in.

To me it all seems right. But you aren't doing this.forceUpdate() which may be done in a different way when using hooks, but Im guessing you may need to force rerender once you show the messages for the first time?

stuyam avatar Jun 10 '19 16:06 stuyam

@sachin8094 The problem is that you are creating a new validator instance on every render. You need to use some other pattern to make sure it's a singleton. The simplest workaround I came up with right off the bat is to store the validator in a state hook (don't need a setState since you don't want to change it).

const [validator] = React.useState(new SimpleReactValidator({...}))

It might make sense to useMemo or useEffect. I wasn't sure about useMemo though since the docs say "You may rely on useMemo as a performance optimization, not as a semantic guarantee." Maybe a useEffect would work? I haven't tried but maybe:

let validator; React.useEffect(() => { validator = new SimpleReactValidator({...}); }, []);

I don't know if this would work or when the effect would run, I can test that out later. But I know the first pattern with useState works.

josh-stevens avatar Aug 09 '19 18:08 josh-stevens

You can use useRef for this kind of job. https://stackoverflow.com/a/53146714/5160696

peyloride avatar Aug 12 '19 19:08 peyloride

@sachin8094 Did you try to validate your input in the onBlur event? It works for me.

  const [address, setAddress] = useState()
  const simpleValidator = useRef(new SimpleReactValidator())

  <Input
    name="name"
    value={companyInformation.name}
    onChange={handleInputChange}
    onBlur={simpleValidator.current.showMessageFor('name')} />
  {simpleValidator.current.message('name', companyInformation.name, 'required')}

santosgabriel avatar Sep 18 '19 14:09 santosgabriel

You can also use force update after a form submit to show all the messages at once:

const simpleValidator = useRef(new SimpleReactValidator())
const [, forceUpdate] = useState();

submitForm() => {
  const formValid = simpleValidator.current.allValid()
  if (!formValid) {
    simpleValidator.current.showMessages()
    forceUpdate(1)
  }
}

santosgabriel avatar Sep 19 '19 13:09 santosgabriel

If you can use forceUpdate with hooks like that then you can initialize the SRV with forceUpdate so it will do it automatically. something like this:

const [, forceUpdate] = useState()
const simpleValidator = useRef(new SimpleReactValidator({autoForceUpdate: {forceUpdate: forceUpdate}))

submitForm() => {
  const formValid = simpleValidator.current.allValid()
  if (!formValid) {
    simpleValidator.current.showMessages()
  }
}

stuyam avatar Sep 19 '19 15:09 stuyam

I have created one small hooks to solve this problem. It worked for me. Here is the repository: https://github.com/chaudharykiran/SimpleReactValidatorWithHooks

chaudharykiran avatar Apr 07 '20 08:04 chaudharykiran

I'm open to adding better support for hooks in SRV. If someone wants to open a PR or talk about some ideas I am open to it.

stuyam avatar Apr 07 '20 14:04 stuyam

I want to work on that. cc. @stuyam

chaudharykiran avatar Apr 08 '20 09:04 chaudharykiran

@chaudharykiran great! I don't use hooks so someone else would be better suited for the main ideas of what this needs. What is SRV missing that would make it work easier with hooks?

stuyam avatar Apr 08 '20 13:04 stuyam

I have created one small hooks to solve this problem. It worked for me. Here is the repository: https://github.com/chaudharykiran/SimpleReactValidatorWithHooks

@chaudharykiran please how do I use showMessageFor on this?

dinorhythms avatar Apr 08 '20 20:04 dinorhythms

If you can use forceUpdate with hooks like that then you can initialize the SRV with forceUpdate so it will do it automatically. something like this:

const [, forceUpdate] = useState()
const simpleValidator = useRef(new SimpleReactValidator({autoForceUpdate: {forceUpdate: forceUpdate}))

submitForm() => {
  const formValid = simpleValidator.current.allValid()
  if (!formValid) {
    simpleValidator.current.showMessages()
  }
}

Does not show error messages after submitting form. Shows only when the form is submitted and then the input is changed.

Update: Working. Didn't call forceUpdate.

arifszn avatar Apr 11 '20 15:04 arifszn

In my case a hook's solution is working nice. Thanks @chaudharykiran

h4r3en avatar Apr 17 '20 13:04 h4r3en

Awesome good job @h4r3en

chaudharykiran avatar Apr 17 '20 15:04 chaudharykiran

please how do I use showMessage on this?

You have to pass true when you want to show error message.

if (notValid) {
   showMessage(true)
}

cc. @dinorhythms

chaudharykiran avatar Apr 17 '20 15:04 chaudharykiran

If you can use forceUpdate with hooks like that then you can initialize the SRV with forceUpdate so it will do it automatically. something like this:

const [, forceUpdate] = useState()
const simpleValidator = useRef(new SimpleReactValidator({autoForceUpdate: {forceUpdate: forceUpdate}))

submitForm() => {
  const formValid = simpleValidator.current.allValid()
  if (!formValid) {
    simpleValidator.current.showMessages()
  }
}

Does not show error messages after submitting form. Shows only when the form is submitted and then the input is changed.

Update: Working. Didn't call forceUpdate.

It does work if you wrap like this:

const useValidator = (customMessage = {}, customValidator = {}): [SimpleReactValidator, Function] => {
  const [, forceUpdate] = useState();

  const validator = useRef(new SimpleReactValidator({
    messages: customMessage, 
    validators: customValidator,
    autoForceUpdate: { forceUpdate: () => forceUpdate(1) }
  }));

  return [validator.current, forceUpdate];
}

garethfentimen avatar Jun 22 '20 14:06 garethfentimen

1

save my day!

LucSouza avatar Jul 13 '20 13:07 LucSouza

I found my own solution without using autoForceUpdate by using focus, hope this will help:

State:

// SET FOCUS
    const [focusPhoneNumber, setFocusPhoneNumber] = useState(false);

Input:

<input
name="phoneNumber"
value={phoneNumber}
onChange={(e) => handlePhoneNumberChange(e)}
onFocus={(e) => setFocusPhoneNumber(true)}
onBlur={focusPhoneNumber ? simpleValidator.current.showMessageFor('phoneNumber') : () => {}}
placeholder="2984928xxx" type="number" />

{ simpleValidator.current.message('phoneNumber', phoneNumber, 'required|min:9|max:14|numeric') }

and on submit button:

let isValid = validator.current.allValid();

 if(!isValid) {
       setFocusPhoneNumber(true);
       validator.current.showMessages(true);
 } 

farisabundev avatar Oct 08 '20 08:10 farisabundev

Here is a more advance custom hook to force rerender the component. This hook will not recreate a SimpleReactValidator instance in each rerender, tiny performance boost~

//useSimpleReactValidator.js
export default function useSimpleReactValidator(passInOptions = {}) {
    const [{ options }, forceUpdate] = React.useReducer(({ options }) => ({ options }), {
        options: passInOptions,
    });
    const simpleValidator = React.useMemo(
        () =>
            new SimpleReactValidator(
                options.autoForceUpdate
                    ? {
                          ...options,
                          autoForceUpdate: {
                              forceUpdate,
                          },
                      }
                    : options
            ),
        [options]
    );
    return [simpleValidator, forceUpdate];
}

//example
const [validator, forceUpdate] = useSimpleReactValidator({...anyOtherOptions, autoForceUpdate: true});

wztech0192 avatar Oct 18 '20 04:10 wztech0192

I have created one small hooks to solve this problem. It worked for me. Here is the repository: https://github.com/chaudharykiran/SimpleReactValidatorWithHooks

This is perfect! Worked like a charm. Appreciate you for adding this.

albahmed23 avatar Jan 09 '21 00:01 albahmed23

@sachin8094 Did you try to validate your input in the onBlur event? It works for me.

  const [address, setAddress] = useState()
  const simpleValidator = useRef(new SimpleReactValidator())

  <Input
    name="name"
    value={companyInformation.name}
    onChange={handleInputChange}
    onBlur={simpleValidator.current.showMessageFor('name')} />
  {simpleValidator.current.message('name', companyInformation.name, 'required')}

I try this but it's showing errors on page load not on onblur

pooja-yadav-ctrl avatar Feb 23 '21 13:02 pooja-yadav-ctrl

@sachin8094 Did you try to validate your input in the onBlur event? It works for me.

const [address, setAddress] = useState()

const simpleValidator = useRef(new SimpleReactValidator())

<Input

name="name"
value={companyInformation.name}
onChange={handleInputChange}
onBlur={simpleValidator.current.showMessageFor('name')} />

{simpleValidator.current.message('name', companyInformation.name, 'required')}

I try this but it's showing errors on page load not on onblur

you need to wrap onBlur={simpleValidator.current.showMessageFor('name')} /> with a function.

onBlur={()=>simpleValidator.current.showMessageFor('name')} />

wztech0192 avatar Feb 23 '21 13:02 wztech0192

@sachin8094 Did you try to validate your input in the onBlur event? It works for me.

const [address, setAddress] = useState()

const simpleValidator = useRef(new SimpleReactValidator())

<Input

name="name"
value={companyInformation.name}
onChange={handleInputChange}
onBlur={simpleValidator.current.showMessageFor('name')} />

{simpleValidator.current.message('name', companyInformation.name, 'required')}

I try this but it's showing errors on page load not on onblur

you need to wrap onBlur={simpleValidator.current.showMessageFor('name')} /> with a function.

onBlur={()=>simpleValidator.current.showMessageFor('name')} />

thanks it's working but after filling the correct input message was still there

pooja-yadav-ctrl avatar Mar 05 '21 05:03 pooja-yadav-ctrl

I have created one small hooks to solve this problem. It worked for me. Here is the repository: https://github.com/chaudharykiran/SimpleReactValidatorWithHooks

Awesome! This works for me. 🚀

shal0mdave avatar Nov 06 '21 06:11 shal0mdave

Good work @shal0mdave

chaudharykiran avatar Nov 10 '21 11:11 chaudharykiran

This works!

const [, forceUpdate] = useReducer((x) => x + 1, 0); const validator = useRef( new SimpleReactValidator({ autoForceUpdate: { forceUpdate: forceUpdate }}) );

Usage: onBlur={validator.current.showMessageFor('name')} />

SavithaDhatchinamoorthy avatar Jan 31 '22 15:01 SavithaDhatchinamoorthy