如何在我的 React 表单提交处理程序中正确捕获和解析错误?

How do I catch and parse errors properly in my React form submission handler?

我正在使用 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 });
  });
}

但是,我在从响应中正确获取错误时遇到了问题。发生错误时,我的端点 returns 带有错误文本的 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(错误请求)”。捕获错误文本并将其保存以供将来解析的正确方法是什么?如果我的端点需要以不同方式格式化数据,它应该做什么(使用 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)}

其实fetchAPI和其他的有点不一样。你应该传递另一个 .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;

你并没有真正解释你想对回复做什么。但是根据您对 throw new Error 的使用,我假设您需要以下 .catch 调用来处理它。在下面的解决方案中,errors 将从 JSON 响应中分配对象。

response.json()return是一个承诺。如果你想 "throw" 该值作为错误,你必须做这样的事情(而不是 throw new Error(...)):

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

.then 回调中返回被拒绝的承诺会导致由所述 .then 调用编辑的承诺 return 也被拒绝。

在上下文中:

 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 值执行任何操作,因此 if 语句中的 return response.json() 不是必需的。您可以将该调用重写为:

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

If my endpoint needs to format the data differently, what should it do (using Django/Python 3.7)?

由于我们不知道您的组件需要什么结构,因此我们可以提供的建议不多。 –

Response.ok属性API指出:

Response.ok Read only

A boolean indicating whether the response was successful (status in the range 200–299) or not.

这意味着即使 response.ok 为假,response.json() 也会 return 数据。

Body.json()

Takes a Response stream and reads it to completion. It returns a promise that resolves with the result of parsing the body text as JSON.

因此,在您的代码中,您应该将第一个获取解析定义为异步,如果响应不是 ok,则 throw 使用 [=22] 解析 response.json() =]:

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 in this Stackblitz workspace检查测试示例。

If my endpoint needs to format the data differently, what should it do (using Django/Python 3.7)?

您必须通过提供一些代码和解释来让我们更多地了解您的端点如何处理请求。

更新

关于您的组件并显示错误,结果 JSON return 是每个字段的错误数组。如果只有一个错误,则将端点更改为 return 字符串而不是数组,或者仅显示第一个错误。如果您有多个错误,那么您可以通过每个字段的所有错误数组进行映射和渲染:

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>
  )
}

让catch部分捕获失败错误,然后在then部分捕获你需要的错误

  }).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 });
  });

在另一端,您可以检查数组中的第一个条目以确定查询是成功还是失败,然后适当地处理消息

fetch API Response 对象代表 Response meta - 连接信息,headers 之类的东西。

现阶段尚未阅读正文。

response.statusText 是来自 HTTP 响应状态行,例如 200 Ok400 (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(...);