dockwa / simple-react-validator

A simple react form validator inspired by Laravel validation.
https://dockwa.github.io/simple-react-validator
MIT License
278 stars 78 forks source link

Validation not working in functional component with hooks #97

Open sachin8094 opened 5 years ago

sachin8094 commented 5 years ago

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>
  );
}
stuyam commented 5 years ago

@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?

josh-stevens commented 5 years ago

@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.

peyloride commented 5 years ago

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

santosgabriel commented 5 years ago

@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 commented 5 years ago

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)
  }
}
stuyam commented 5 years ago

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()
  }
}
chaudharykiran commented 4 years ago

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

stuyam commented 4 years ago

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.

chaudharykiran commented 4 years ago

I want to work on that. cc. @stuyam

stuyam commented 4 years ago

@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?

dinorhythms commented 4 years ago

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?

arifszn commented 4 years ago

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.

h4r3en commented 4 years ago

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

chaudharykiran commented 4 years ago

Awesome good job @h4r3en

chaudharykiran commented 4 years ago

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

garethfentimen commented 4 years ago

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];
}
LucSouza commented 4 years ago
1

save my day!

farisabundev commented 4 years ago

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);
 } 
wztech0192 commented 4 years ago

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});
albahmed23 commented 3 years ago

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.

pooja-yadav-ctrl commented 3 years ago

@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

wztech0192 commented 3 years ago

@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')} />

pooja-yadav-ctrl commented 3 years ago

@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

shal0mdave commented 3 years ago

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. 🚀

chaudharykiran commented 3 years ago

Good work @shal0mdave

SavithaDhatchinamoorthy commented 2 years ago

This works!

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

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