我正在构建一个 React 应用程序,用于记录用户输入并将其保存到数据库中。我使用 node.js Express 创建了一个 REST API 将应用程序连接到数据库。
我已使用 Postman 成功发出发布请求,但无法使其与 React 应用程序一起使用,因为我收到此错误:
语法错误:JSON 输入意外结束
我已经尝试过类似 Stack Overflow 帖子中描述的解决方案,但没有任何效果对我有用。据我所知,我已经正确格式化了 JSON 输入。一篇文章指出这可能与我的 Express API 未返回格式正确的 JSON 有关,但事实并非如此,因为邮递员在成功发布后会收到 JSON 输出。
邮递员输出:
控制台输出:
堆栈跟踪:
反应应用程序代码:
import logo from './logo.svg';
import './App.css';
import Submit from './Submit';
import React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
pic: "",
alive: "",
typeOfDeath: "",
comment: "",
geoLocation: ""
};
}
// code credit - https://github.com/lyhd/reactjs/blob/base64_encode/src/index.js
encodeFileBase64 = (file) => {
var reader = new FileReader();
if (file) {
reader.readAsDataURL(file);
reader.onload = () => {
var Base64 = reader.result;
console.log(Base64);
//setFileBase64String(Base64);
};
reader.onerror = (error) => {
console.log("error: ", error);
};
}
};
handleSubmit = async (evt) => {
evt.preventDefault();
const input = document.querySelector('input[type=file]');
const pic = input.files[0];
const pic_base64 = this.encodeFileBase64(pic);
const rbs = document.querySelectorAll('input[name="mortality"]');
let alive = false;
//code belongs to javascripttutorial.net/javascript-dom/javascript-radio-button/
for (const rb of rbs) {
if (rb.checked) {
alive = rb.value;
break;
}
}
const typeOfDeathDropDown = document.querySelector('#typeOfDeath');
const typeOfDeath = typeOfDeathDropDown.options[typeOfDeathDropDown.selectedIndex].value;
const comment = document.querySelector('#comment').value.trim();
const geoLocation = "placeholder";
//pleaceholder validation if statement, replace at a later date
if (1 > 0) {
console.log(alive,typeOfDeath,comment,geoLocation);
this.setState({
pic: pic_base64,
alive: alive,
typeOfDeath: typeOfDeath,
comment: comment,
geoLocation: geoLocation
});
const url = 'https://zapp.ogs17.brighton.domains/';
let jsonBody = JSON.stringify({
pic: pic_base64,
alive: alive,
typeOfDeath: typeOfDeath,
comment: comment,
geoLocation: geoLocation
});
console.log(jsonBody);
try {
const response = await fetch(url, {
method: 'POST',
headers: {'Content-Type':'application/x-www-form-urlencoded'},
body: jsonBody
});
await console.log(response);
const jsonData = await response.json();
this.setState({
loading: false,
records: jsonData.records
});
} catch (err) {
console.log(err);
this.setState({
loading: false,
records: []
});
}
}
}
render = () => {
return (
<div>
<h1>Zapp App - Pangolin Sightings</h1>
<form onSubmit={this.handleSubmit}>
<input type="file" accept="image/*" id="pic" />
<input id="alive" type="radio" name="mortality" />
<label htmlFor="alive">Alive</label>
<input id="deceased" type="radio" name="mortality" />
<label htmlFor="deceased">Deceased</label>
<br />
<label htmlFor="typeOfDeath">Type of Death:</label>
<select name="typeOfDeath" id="typeOfDeath">
<option value="fence_electrocution">Fence death: electrocution;</option>
<option value="fence_normal">Fence death: caught on non-electrified fence</option>
<option value="road">Road death</option>
<option value="other">Other</option>
</select>
<br />
<textarea name="comment" id="comment" defaultValue="comment"></textarea>
<br />
<button type="submit">Submit</button>
</form>
<Submit state={this.state} />
</div>
);
}
}
export default App;
Node.js Express API 代码:
const express = require('express');
const bodyParser = require('body-parser');
const db = require('./db');
const cors = require('cors');
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors());
const port = 3000;
//https://stackoverflow.com/questions/18310394/no-access-control-allow-origin-node-apache-port-issue
app.use(function (req, res, next) {
// Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', '*');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', true);
// Pass to next layer of middleware
next();
});
async function getSighting(req) {
let status = 500, data = null;
try {
const oid = req.query.oid;
if (oid
&& oid.length > 0 && oid.length <= 32
&& oid.match(/^[a-z0-9]+$/i)) {
const sql = 'SELECT * FROM tSightings WHERE oid=?';
const rows = await db.query(sql, [oid]);
if (rows) {
status = 200;
data = {
'oid': oid,
'data': rows
};
} else {
status = 204;
}
} else {
status = 400;
}
} catch (e) {
console.error(e);
}
return { status, data };
}
async function postSighting(req) {
console.log("postSighting method entered")
let status = 500, data = null;
try {
const pic = req.body.pic;
const alive = req.body.alive;
const typeOfDeath = req.body.typeOfDeath;
const comment = req.body.comment;
const geoLocation = req.body.geoLocation;
//impliment appropriate checks here
if (1 == 1) {
const sql = 'INSERT INTO tSightings (pic, alive, typeOfDeath, comment, geoLocation) '
+ 'VALUES (?, ?, ?, ?, ?)';
const result = await db.query(sql, [pic, alive, typeOfDeath, comment, geoLocation]);
if (result.affectedRows) {
status = 201;
data = { 'id': result.insertId };
}
} else {
status = 400;
}
} catch (e) {
console.error(e);
}
return { status, data };
}
app.get('/', async (req, res) => {
console.log("express get submitted")
const { status, data } = await getSighting(req);
res.status(status);
if (data) res.json(data);
else res.end();
})
app.listen(port, () => {
console.log(`Running at http://localhost:${port}`)
})
app.get('/express_api', async (req, res) => {
console.log("express get submitted")
const { status, data } = await getData(req);
res.status(status);
if (data) res.json(data);
else res.end();
})
app.post('/', async (req, res) => {
const { status, data } = await postSighting(req);
res.status(status);
if (data) res.json(data);
else res.end();
})
app.put('/express_api', async (req, res) => {
res.status(405);
res.end();
})
app.delete('/express_api', async (req, res) => {
res.status(405);
res.end();
})
前端
当遇到网络错误或服务器端 CORS 配置错误时,fetch() Promise 将拒绝并返回 TypeError。
您正在发送标头内容类型 = form-urlencoded 和实际正文作为 json
try {
const response = await fetch(url, {
method: 'POST',
//should be application json if your're stringifying it
headers: {'Content-Type':' 'application/json'},
body: jsonBody
});
await console.log(response);
// need to check if the response is valid or not
if(response.ok){
const jsonData = await response.json();
}else{
throw response
}
this.setState({
loading: false,
records: jsonData.records
});
}
后端
我不知道为什么你要设置自定义标头,同时使用允许所有方法的 cors
app.use(cors());
。
删除它
//was missing
app.use(bodyParser.json());
const corsOptions = {
origin: "*" // grant for all origins. can be use as per your ref url
}
app.use(cors(corsOptions));
// don't need this part
app.use(function (req, res, next) {
// Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', '*');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', true);
// Pass to next layer of middleware
next();
});