Как правильно перехватывать и анализировать ошибки в обработчике отправки формы React? - PullRequest
2 голосов
/ 25 марта 2020

Я использую 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)}

Ответы [ 5 ]

3 голосов
/ 27 марта 2020

API-интерфейс свойства Response.ok гласит:

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

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

Это означает, что даже если response.ok неверно, 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>
  )
}
0 голосов
/ 03 апреля 2020

Объекты fetch API Response представляют информацию мета-соединения Response, заголовки и тому подобное.

На этом этапе тело не было прочитано.

response.statusText - это строка состояния ответа HTTP, например, 200 Ok или 400 (Bad Request) в вашем случае.

response.ok является помощником для проверки, если response.status >= 200 && response.status < 300

Пока что ничего из того, что мы рассмотрели здесь, не является содержанием ответа.

Для этого вам нужно использовать response.json(), response.text() или response.blob(). Пока существует тело, любая из этих функций может использоваться для его чтения - независимо от кода статуса HTTP.

Каждая из этих функций читает тело, отправленное вам сервером, и выполняет какие-то дополнительная обработка (проверьте MDN или предпочитаемый вами источник документации для получения дополнительной информации).

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

fetch(...)
    .then(response => {
        if (response.ok) {
            return response.json();
        }
        if (response.status === 400) {
            return Promise.reject(response.json());
        }
        return Promise.reject(new Error(`Unexpected HTTP Status: ${response.status} - ${response.statusText}`));
    })
    .then(...)
    .catch(...);
0 голосов
/ 02 апреля 2020

позволяет части catch перехватывать ошибки и затем перехватывать нужные вам ошибки в тогдашней части

  }).then(response => {
       //use a switch to got through the responses or an if statement 
        var response = JSON.Parse(response)
        //here am assuming after you console log response you gt the correct array placement
        if(response[0]==200){
           // this indicates success
              return  JSON.stringify({"success":"true","message":"success message";})
         }else if(response[0]==400){
           // this indicates error
              return  JSON.stringify({"success":"false","message":"Error message";})
           }
  }).catch(errors => {
      console.log(errors);
      this.setState({ errors });
  });

на другом конце вы можете проверить первую запись в массиве, чтобы определить, был ли запрос успешным или неудачным, затем обработать сообщение соответствующим образом

0 голосов
/ 27 марта 2020

Вы на самом деле не объясняете, что вы хотите сделать с ответом. Но исходя из вашего использования для 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)?

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

0 голосов
/ 27 марта 2020

На самом деле 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 Массив, и вы должны показать это так, как показано ниже: Я использую реструктуризацию для реквизита:

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;

...