Как правильно перехватывать и анализировать ошибки в обработчике отправки формы React?

Я использую React 16.13.0. У меня есть следующая функция для обработки событий отправки:

handleFormSubmit(e) {
  e.preventDefault();
  const NC = this.state.newCoop;
  delete NC.address.country;

  fetch('/coops/',{
      method: "POST",
      body: JSON.stringify(this.state.newCoop),
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
  }).then(response => {
      if (response.ok) {
          return response.json();
      }
      console.log(response.json());
      console.log(response.body);
      throw new Error(response.statusText);
  }).catch(errors => {
      console.log(errors);
      this.setState({ errors });
  });
}

Однако у меня возникла проблема с получением ошибок из ответа. При возникновении ошибки моя конечная точка возвращает запрос 400 с текстом ошибки. Вот что происходит в curl:

curl --header "Content-type: application/json" --data "$req" --request POST "http://localhost:9090/coops/"
{"phone":["The phone number entered is not valid."]}

Но response.statusText содержит «400 (Bad Request)». Как правильно перехватить текст ошибки и сохранить его для дальнейшего разбора? Если моей конечной точке нужно по-разному форматировать данные, что она должна делать (используя Django / Python 3.7)?

Редактировать:

Это компонент ввода, в котором я пытаюсь отобразить ошибки:

<Input inputType={'text'}
    title = {'Phone'}
    name = {'phone'}
    value = {this.state.newCoop.phone}
    placeholder = {'Enter phone number'}
    handleChange = {this.handleInput}
    errors = {this.state.errors}
/> 

И код входного компонента, src/Input.jsx

import React from 'react'
import {FormControl, FormLabel} from 'react-bootstrap'

const Input = (props) => {
  return (
    <div className="form-group">
      <FormLabel>{props.title}</FormLabel>
      <FormControl
          type={props.type}
          id={props.name}
          name={props.name}
          value={props.value}
          placeholder={props.placeholder}
          onChange={props.handleChange}
      />

      {props.errors && props.errors[props.name] && (
          <FormControl.Feedback>
              <div className="fieldError">
                  {props.errors[props.name]}
              </div>
          </FormControl.Feedback>
      )}
    </div>
  )
}

export default Input;

Когда я запускаю console.log(errors) они выглядят так:

{phone: Array(1), web_site: Array(1)}

Всего 3 ответа


Вы на самом деле не объясняете, что вы хотите сделать с ответом. Но исходя из вашего использования для throw new Error я предполагаю, что вы хотите, чтобы следующий вызов .catch с этим. В приведенном ниже решении errors будет назначен объект из ответа JSON.

response.json() возвращает обещание. Если вы хотите «выбросить» это значение как ошибку, вы должны сделать что-то вроде этого (вместо throw new Error(...) ):

return response.json().then(x => Promise.reject(x))

Возврат отклоненного обещания от обратного вызова .then приводит к тому, что обещания, возвращенные указанным вызовом .then также будут отклонены.

В контексте:

 fetch('/coops/',{
        method: "POST",
        body: JSON.stringify(this.state.newCoop),
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
    }).then(response => {
        if (response.ok) {
            return response.json();
        }
        return response.json().then(x => Promise.reject(x));
    }).catch(errors => {
        console.log(errors);
        this.setState({ errors });
    });

Примечание. Поскольку вы ничего не делаете с возвращаемым значением успешного ответа, return response.json() внутри оператора if не требуется. Вы можете переписать этот призыв к:

.then(response => {
  if (!response.ok) {
    return response.json().then(x => Promise.reject(x));
  }
})

Если моей конечной точке нужно по-разному форматировать данные, что она должна делать (используя Django / Python 3.7)?

Поскольку мы не знаем, какую структуру ожидает ваш компонент, мы не можем многое предложить. -


API свойства Response.ok утверждает, что:

Response.ok Только для чтения

Логическое значение, указывающее, был ли ответ успешным (состояние в диапазоне 200–299) или нет.

Это означает, что даже response.ok имеет значение false, response.json() вернет данные.

Body.json()

Принимает поток Response и считывает его до конца. Он возвращает обещание, которое разрешается в результате анализа основного текста как JSON .

Итак, в вашем коде вы должны определить разрешение при первом извлечении для асинхронного, и если ответ не в ok , то throw с разрешенным response.json() используя await :

handleFormSubmit(e) {
  e.preventDefault();
  const NC = this.state.newCoop;
  delete NC.address.country;

  fetch('/coops/',{
      method: "POST",
      body: JSON.stringify(this.state.newCoop),
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
  }).then(async response => { // Define the first resolve to an asynchronous function
      if (response.ok) {
          // If it's OK, resolve JSON and return the actual data
          return await response.json();
          // or better set react state
          // const data = await response.json();
          // this.setState({ data });
      } else {
          // It's not OK, throw an error with the JSON data
          // so you'll be able to catch
          throw await response.json();
      }
  }).catch(errors => {
      // Here you should get the actual errors JSON response
      console.log(errors);
      this.setState({ errors });
  });
}

Вы можете проверить тестовый пример, работающий с использованием fetch-mock в этом рабочем пространстве Stackblitz .

Если моей конечной точке нужно по-разному форматировать данные, что она должна делать (используя Django / Python 3.7)?

Вам нужно будет сообщить нам больше о том, как ваша конечная точка обрабатывает запросы, предоставив некоторый код и пояснения.

ОБНОВИТЬ

Что касается вашего компонента и отображения ошибок, в результате JSON возвращает массив ошибок для каждого поля. Если у вас будет только одна ошибка, измените конечную точку, чтобы она возвращала строку вместо массива или отображала только первую ошибку. Если у вас будет несколько ошибок, вы можете отобразить и отобразить массив всех ошибок для каждого поля:

const Input = (props) => {
  return (
    <div className="form-group">
      <FormLabel>{props.title}</FormLabel>
      <FormControl
          type={props.type}
          id={props.name}
          name={props.name}
          value={props.value}
          placeholder={props.placeholder}
          onChange={props.handleChange}
      />

      // If you just want to display the first error
      // then render the first element of the errors array
      {props.errors && props.errors[props.name] && (
        <FormControl.Feedback>
          <div className="fieldError">
            {props.errors[props.name][0]}
          </div>
        </FormControl.Feedback>
      )}

      // Or if you may have multiple errors regarding each field
      // then map and render through all errors
      {/*
      {props.errors && props.errors[props.name] && (
        <FormControl.Feedback>
          {props.errors[props.name].map((error, index) => (
            <div key={`field-error-${props.name}-${index}`} className="fieldError">
              {error}
            </div>
          ))}
        </FormControl.Feedback>
      )}
      */}
    </div>
  )
}

На самом деле API fetch немного отличается от других. Вы должны передать другой .then() для получения данных, а затем проанализировать их, и с помощью нескольких обратных вызовов сделать коды трудно читаемыми, я использую async/await для обработки ошибки:

async handleFormSubmit(e) {
  e.preventDefault();
  const NC = this.state.newCoop;
  delete NC.address.country;

  try {
    const response = await fetch('/coops/',{
      method: "POST",
      body: JSON.stringify(this.state.newCoop),
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
    });

    if (response.ok) {
      const result = await response.json();
      console.log('_result_: ', result);
      return result;
    }

    throw await response.json();

  } catch (errors) {

    console.log('_error_: ', errors);
    this.setState({ errors });
  }
}

Обновление для вашего нового вопроса:

Определенно, это другой вопрос, почему ошибка не появляется, на самом деле, ошибка phone и JavaScript Array, и вы должны показать это по-своему, как показано ниже, я использую назначение реструктуризации для реквизитов:

import React from 'react'
import { FormControl, FormLabel } from 'react-bootstrap'

const Input = ({
  title,
  type,
  name,
  value,
  placeholder,
  handleChange,
  errors,
}) => (
  <div className="form-group">
    <FormLabel>{title}</FormLabel>
    <FormControl
      type={type}
      id={name}
      name={name}
      value={value}
      placeholder={placeholder}
      onChange={handleChange}
    />
    {errors && errors[name] && (
      <FormControl.Feedback>
        {errors[name].map((err, i) => (
          <div key={err+i} className="fieldError">{err}</div>
        ))}
      </FormControl.Feedback>
    )}
  </div>
);

export default Input;


Есть идеи?

10000