1

I have a Register form Component and I want every time the submit button clicked, it will reset the errors validation object and fill with the new one. Since setErrors({}) is an async function, how to do the next validation flow after the state resetting is really done?

const [name, setName] = React.useState('');
const [email, setEmail] = React.useState('');
const [errors, setErrors] = React.useState<any>({});

function handleSubmit() {
  setErrors({}); // the resetting part

  // Do belows after setErrors({}) is really done
  let newErrors = {};

  if (name.trim() === '') {
    newErrors = {...newErrors, name: 'Name cannot be empty'};
  } else if (name.trim().length < 3) {
    newErrors = {
      ...newErrors,
      name: 'Name length cannot be less than 3 characters',
    };
  }

  if (email.trim() === '') {
    newErrors = {...newErrors, email: 'Email cannot be empty'};
  } else if (
    !/^([a-zA-Z0-9_.-])+@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/.test(email)
  ) {
    newErrors = {...newErrors, email: 'Email is not valid'};
  }

  setErrors(newErrors);
}

<Button
  mode="contained"
  style={{borderRadius: 0}}
  contentStyle={{padding: 10}}
  uppercase={false}
  onPress={handleSubmit}>
  Daftar
</Button>
Justinus Hermawan
  • 1,194
  • 1
  • 23
  • 44
  • What makes you think `setErrors` is an `async` function? I can assure you it is not. Typically you would use an `useEffect` hook with a dependency on the value update you want the effect callback to be run after. What is wrong with just creating that `newErrors` empty object and updating the `errors` state when the function completes? With the two `setErrors` calls in the same function like that the second one overwrites the first anyway, like it never happened. – Drew Reese Dec 08 '20 at 08:42
  • Because I think it's same with the non-hooks React's `setState(...)`, isn't it? – Justinus Hermawan Dec 08 '20 at 08:47
  • `this.setState` isn't an `async` function either. They do both work within the React component lifecycle though. – Drew Reese Dec 08 '20 at 08:47
  • I read https://stackoverflow.com/a/36087156/3087119 and it said that `setState` actions are asynchronous and are batched for performance gain. So, I think the statements right below of `setState(...)` call are executed before the `setState()` is really done. – Justinus Hermawan Dec 08 '20 at 08:51
  • 1
    Asynchronous code isn't the same thing as an `async` declared function. React state updates are asynchronous, meaning, they are enqueued during a render cycle and batch processed for the next render cycle. State is `const` *during* a render cycle. The functions that enqueue the state update are 100% good old-fashioned synchronous functions. The code below `setErrors` is executed before state updates, but that doens't matter because you enqueue another state update that overwrites the first one. – Drew Reese Dec 08 '20 at 08:56
  • Oh I see now, what I mean earlier is `asynchronous processing` instead, not an `async` function, sorry for the wrong use of the term. Yes you're right, I could just remove the first `setErrors({})` statement and the things are done. But if next time I really need to be like that, should I just use `useEffect` and put `errors` as the dependency param for `useEffect` hooks to be watched for? – Justinus Hermawan Dec 08 '20 at 09:04
  • No worries, just a bit of a pet peeve of mine when people say something is "async" which leaves the window open to ambiguity if they are referring specifically to `async` functions or more generally to asynchronous code, which can be something as simple as a callback. Yes, if you want to run an effect of "doing *something*" after a chunk of state updates then `useEffect` with correct dependency is the functional component equivalent to a class-based component's `componentDidUpdate` method. – Drew Reese Dec 08 '20 at 09:08
  • Owh nice, I used to use this way a lot when I wanted to do something right after `this.setState(...)` was finished: `setState({ ... }, () => doSomething());`, so in this case I would just turn it into `useEffect(() => { let newErrors = {}; .... setErrors(newErrors); }, [errors]);`, am I right? – Justinus Hermawan Dec 08 '20 at 09:18
  • Not quite, that would create an infinite loop since the effect callback updates the value that triggers the effect callback. Just about *any other* dependency would be ok though. – Drew Reese Dec 08 '20 at 09:21
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/225658/discussion-between-drew-reese-and-justinus-hermawan). – Drew Reese Dec 08 '20 at 09:26

1 Answers1

0

I suggest to add state property called submitted when it's true it allows the errors show and also watch the input values to fill the errors :

import React from 'react';

const Untitled = () => {
    const [name, setName] = React.useState('');
    const [email, setEmail] = React.useState('');
    const [errors, setErrors] = React.useState < any > {};
    const [submitted, setSubmitted] = React.useState(false);
    function handleSubmit() {
        setSubmitted(true);
    }

    React.useEffect(() => {
        let newErrors = {};

        if (name.trim() === '' && submitted) {
            newErrors = { ...newErrors, name: 'Name cannot be empty' };
        } else if (name.trim().length < 3) {
            newErrors = {
                ...newErrors,
                name: 'Name length cannot be less than 3 characters',
            };
        }

        if (email.trim() === '' && submitted) {
            newErrors = { ...newErrors, email: 'Email cannot be empty' };
        } else if (!/^([a-zA-Z0-9_.-])+@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/.test(email)) {
            newErrors = { ...newErrors, email: 'Email is not valid' };
        }

        setErrors(newErrors);
    }, [name, email, submitted]);

    React.useEffect(() => {
        if (!errors.name && !errors.email) {
            setSubmitted(false);
        }
    }, [errors]);

    return (
        <div>
            <input type="text" value={name} onChange={e => setName(e.target.value)} />

            <input type="email" value={email} onChange={e => setEmail(e.target.value)} />
            <Button
                mode="contained"
                style={{ borderRadius: 0 }}
                contentStyle={{ padding: 10 }}
                uppercase={false}
                onPress={handleSubmit}
            >
                Daftar
            </Button>

            {submitted && JSON.stringify(errors)}
        </div>
    );
};

export default Untitled;


Boussadjra Brahim
  • 82,684
  • 19
  • 144
  • 164