31 votes

Comment utiliser correctement la méthode setError de Formik ? (bibliothèque React)

J'utilise React pour communiquer avec un backend. J'essaie maintenant d'implémenter correctement Formik (bibliothèque de formulaires).

Question principale : Comment utiliser correctement la méthode setError de Formik ?

Les erreurs de validation côté client s'affichent correctement, mais j'essaie maintenant de définir/afficher les erreurs de validation en arrière-plan, qui sont renvoyées avec une réponse au code d'état 400.

Lien à la documentation sur la méthode que j'essaie d'utiliser

J'utilise cette méthode dans la méthode nommée traiter 400Error dans le code ci-dessous.

Mon code React (et Formik) :

import React, { Component } from "react";
import axios from "axios";
import { Formik } from "formik";
import * as Yup from "yup";
import styled from "styled-components";
import FormError from "../formError";

const Label = styled.label``;

class LoginForm extends Component {
  initialValues = {
    password: "",
    username: ""
  };

  getErrorsFromValidationError = validationError => {
    const FIRST_ERROR = 0;
    return validationError.inner.reduce((errors, error) => {
      return {
        ...errors,
        [error.path]: error.errors[FIRST_ERROR]
      };
    }, {});
  };

  getValidationSchema = values => {
    return Yup.object().shape({
      password: Yup.string()
        .min(6, "Password must be at least 6 characters long")
        .required("Password is required!"),
      username: Yup.string()
        .min(5, "Username must be at least 5 characters long")
        .max(40, "Username can not be longer than 40 characters")
        .required("Username is required")
    });
  };

  handleSubmit = async (values, { setErrors }) => {
    console.log("handleSubmit");

    try {
      const response = await axios.post(
        "http://127.0.0.1:8000/rest-auth/login/",
        values
      );
      const loginToken = response.data["key"];
      this.handleLoginSuccess(loginToken);
    } catch (exception) {
      // Expected: 400 status code
      if (exception.response && exception.response.status === 400) {
        // Display server validation errors
        this.handle400Error(exception.response.data, setErrors);
      }
      console.log("exception", exception);
      console.log("exception.response", exception.response);
    }
  };

  handle400Error = (backendErrors, setErrors) => {
    let errors = {};
    for (let key in backendErrors) {
      errors[key] = backendErrors[key][0]; // for now only take the first error of the array
    }
    console.log("errors object", errors);
    setErrors({ errors });
  };

  handleUnexpectedError = () => {};

  handleLoginSuccess = loginToken => {
    console.log("handleLoginSuccess");
    this.props.setGreeneryAppState({
      loginToken: loginToken
    });
    this.props.history.replace(`/${this.props.locale}/`);
  };

  validate = values => {
    const validationSchema = this.getValidationSchema(values);
    try {
      validationSchema.validateSync(values, { abortEarly: false });
      return {};
    } catch (error) {
      return this.getErrorsFromValidationError(error);
    }
  };

  render() {
    return (
      <React.Fragment>
        <h1>Login</h1>
        <Formik
          initialValues={this.initialValues}
          validate={this.validate}
          validationSchema={this.validationSchema}
          onSubmit={this.handleSubmit}
          render={({
            errors,
            touched,
            values,
            handleBlur,
            handleChange,
            handleSubmit
          }) => (
            <form onSubmit={handleSubmit}>
              {errors.non_field_errors && (
                <formError>{errors.non_field_errors}</formError>
              )}
              <Label>Username</Label>
              <input
                onChange={handleChange}
                onBlur={handleBlur}
                value={values.username}
                type="text"
                name="username"
                placeholder="Enter username"
              />
              {touched.username &&
                errors.username && <FormError>{errors.username}</FormError>}
              <Label>Password</Label>
              <input
                onChange={handleChange}
                onBlur={handleBlur}
                value={values.password}
                type="password"
                name="password"
                placeholder="Enter password"
              />
              {touched.password &&
                errors.password && <FormError>{errors.password}</FormError>}
              <button type="submit">Log in</button>
            </form>
          )}
        />
      </React.Fragment>
    );
  }

48voto

jaredpalmer Points 1111

L'auteur de Formik ici...

setError était déprécié dans la v0.8.0 et renommé en setStatus . Vous pouvez utiliser setErrors(errors) ou setStatus(whateverYouWant) dans votre handleSubmit pour obtenir le comportement que vous voulez ici comme ceci :

handleSubmit = async (values, { setErrors, resetForm }) => {
   try {
     // attempt API call
   } catch(e) {
     setErrors(transformMyApiErrors(e))
     // or setStatus(transformMyApiErrors(e))
   }
}

Quelle est la différence entre l'utilisation setStatus vs. setErrors ?

Si vous utilisez setErrors vos erreurs seront effacées par le prochain produit de Formik. validate ou validationSchema qui peut être déclenché par la saisie de l'utilisateur (événement de modification) ou par le flou d'une entrée (événement de flou). Remarque : cela suppose que vous n'avez pas défini manuellement validateOnChange y validateOnBlur props à false (ils sont true par défaut).

IMHO setStatus est en fait idéal ici car il va placer le(s) message(s) d'erreur sur une partie séparée de l'état de Formik. Vous pouvez alors décider comment / quand vous montrez ce message à l'utilisateur final comme ceci.

// status can be whatever you want
{!!status && <FormError>{status}</FormError>}
// or mix it up, maybe transform status to mimic errors shape and then ...
{touched.email && (!!errors.email && <FormError>{errors.email}</FormError>) || (!!status && <FormError>{status.email}</FormError>) }

Soyez conscient que la présence ou la valeur de status n'a aucun impact sur la soumission du prochain formulaire. Formik ne fait qu'interrompre le processus de soumission si la validation échoue .

3voto

redheadedstep Points 163

Une autre façon de gérer cette situation est d'assigner une clé spécifique à l'utilisateur. api erreurs et utilisation setStatus pour les messages d'état.

__handleSubmit = (values, {setStatus, setErrors}) => {
  return this.props.onSubmit(values)
    .then(() => {
      setStatus("User was updated successfully.");
    })
    .catch((err) => {
      setErrors({api: _.get(err, ["message"])});
    });
}

Les erreurs de validation apparaîtraient alors à côté des champs et les erreurs d'API en bas de la page :

<Formik
  validationSchema={LoginSchema}
  initialValues={{login: ""}}
  onSubmit={this.__handleSubmit}
>
  {({isSubmitting, status, errors, values, setFieldValue}) => (
    <Form className={classNames("form")}>
      <FormGroup>
        <InputGroup>
          <InputGroup.Text>
            <FontAwesomeIcon icon={faUser} fixedWidth />
          </InputGroup.Text>
          <Field
            name="login"
            type={"text"}
            placeholder="Login"
            className="form-control"
          />
        </InputGroup>
        <ErrorMessage name="login" />
      </FormGroup>
      <Button type="submit" variant="primary" disabled={isSubmitting}>
        Submit
      </Button>
      {errors && _.has(errors, ["api"]) && <div className="text-danger">{_.get(errors, ["api"])}</div>}
      {status && <div className="text-success">{status}</div>}
    </Form>
  )}
</Formik>

N'oubliez pas le schéma...

const LoginSchema = Yup.object().shape({
  login: Yup.string()
    .min(4, 'Too Short!')
    .max(70, 'Too Long!')
    .required('Login is required'),
});

En api Le message d'erreur s'affichera jusqu'au prochain appel de validation par Formik (i.e. l'utilisateur est en train de corriger quelque chose). Mais le status Le message restera jusqu'à ce que vous l'effaciez (avec une minuterie ou un fondu).

1voto

Rik Schoonbeek Points 124

Je viens de résoudre mon propre problème.

J'avais besoin d'utiliser :

setErrors( errors )

au lieu de :

setErrors({ errors })

0voto

anoNewb Points 154
<Formik
  validationSchema={schema}
  initialValues={{ email: '', pswrd: '' }}
  onSubmit={(values, actions) => {
    // initialise error status <---- 1
    actions.setStatus(undefined); 

    setTimeout(() => {

      // setting error status <---- 2
      actions.setStatus({
        email: 'This is email already exists.',
        pswrd: 'This is password is incorrect',
      });

    }, 500);
  }}
  // destructuring status <---- 3
  render={({ handleSubmit, handleChange, handleBlur, values, errors, status }) => (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        name="email"
        value={values['email']}
        onChange={handleChange}
        onBlur={handleBlur}
      />
      <input
        type="text"
        name="pswrd"
        value={values['pswrd']}
        onChange={handleChange}
        onBlur={handleBlur}
      />
      <button type="submit">Submit</button>

      // using error status <---- 4
      {status && status.email ? (
        <div>API Error: {status.email}</div>
      ) : (
        errors.email && <div>Validation Error: {errors.email}</div>
      )}

      {status && status.pswrd ? (
        <div>API Error: {status.pswrd}</div>
      ) : (
        errors.pswrd && <div>Validation Error: {errors.pswrd}</div>
      )}
    </form>
  )}
/>

0voto

const formik = useFormik({
    initialValues:{
        email:"",password:"",username:""
    },
    validationSchema:validation_schema,
    onSubmit:(values) => {
        const {email,password,username} = values
        // ......
    }
});

formik.setErrors({email:"Is already taken"}) // error message for email field

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X