I'm currently learning the MERN stack and I'm very new to React and Express. My current task is to use an API I previously created and update the JSON data using React.
I created a form component using ReactStrap and now I need POST that data to the JSON object array of my API.
I'm using FormData and so far I've been able to access and display the data correctly but I can't seem so figure out how to update or add any data. With my current code I keep on getting
Uncaught TypeError: Failed to construct 'FormData': parameter 1 is not of type 'HTMLFormElement'.
I've tried troubleshooting for this error but none of the answers I find seem to be applicable and as I'm just starting out with Express and APIs I'm not exactly sure what to look for. Any assistance would be great.
**I'm including a lot of code because I might be overlooking a small detail and want to create a full picture.
My Form component
import React from "react";
import Form from "react-bootstrap/Form";
import Modal from "react-bootstrap/Modal";
import AppButton from "./Button";
const WebForm = ({
show,
setShow,
formTitle,
onSubmit,
onChange,
btnValue,
projID,
idVal,
titleName,
descriptionName,
projURLName,
titleVal,
descriptionVal,
projURLVal,
}) => {
return (
<div>
<Modal
show={show}
onHide={() => setShow(false)}
dialogClassName="modal-90w"
aria-labelledby="example-custom-modal-styling-title"
>
<Modal.Dialog>
<Modal.Header closeButton>
<Modal.Title>{formTitle}</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form onSubmit={onSubmit}>
<Form.Group className="mb-3">
<Form.Label>Project Name</Form.Label>
<Form.Control
type="number"
placeholder="Project ID"
id="projID"
name={projID}
defaultValue={idVal}
onChange={onChange}
/>
<Form.Text className="text-muted">
Please use ALL CAPS for the Project Name.
</Form.Text>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Project Name</Form.Label>
<Form.Control
type="text"
placeholder="Project Name"
id="projTitle"
name={titleName}
defaultValue={titleVal}
onChange={onChange}
/>
<Form.Text className="text-muted">
Please use ALL CAPS for the Project Name.
</Form.Text>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Project Description</Form.Label>
<Form.Control
type="text"
placeholder="Enter Description"
id="projDescription"
name={descriptionName}
defaultValue={descriptionVal}
onChange={onChange}
/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Web Address</Form.Label>
<Form.Control
type="text"
placeholder="Project Web-Address"
id="projURL"
name={projURLName}
defaultValue={projURLVal}
onChange={onChange}
/>
</Form.Group>
<AppButton value={btnValue} type="submit" />
</Form>
</Modal.Body>
</Modal.Dialog>
</Modal>
</div>
);
};
export default WebForm;
The main UI component
import React, { useState, useEffect, useRef } from "react";
import Carousel from "react-bootstrap/Carousel";
import AppButton from "./Button";
import DispCard from "./Card";
import WebForm from "./Form";
const ProjectGrid = () => {
const [index, setIndex] = useState(0);
const [showEdit, setShowEdit] = useState(false);
const [showAdd, setShowAdd] = useState(false);
const [project, setProject] = useState([{}]);
const [formValue, setValue] = useState("");
const form = useRef(null);
console.log(project);
console.log(form);
const fetchProjects = async () => {
const res = await fetch("/projects");
res.json().then((res) => setProject(res));
};
useEffect(() => {
fetchProjects();
}, []);
const handleSelect = (selectedIndex, e) => {
setIndex(selectedIndex);
};
const handleChange = (e) => {
setValue(e.target.value);
console.log(formValue);
};
const submit = (e) => {
e.preventDefault();
const data = new FormData(form.current);
console.log(project);
console.log(data);
fetch("/projects", {
method: "POST",
body: data,
})
.then((res) => res.json())
.then((json) => setProject(json.project));
console.log(project);
console.log(data);
};
return (
<div>
<Carousel activeIndex={index} onSelect={handleSelect} interval={null}>
{project.map((item, index) => {
return (
<Carousel.Item key={index}>
<DispCard
onEdit={() => setShowEdit(true)}
title={item.title}
description={item.description}
URL={item.URL}
/>
<Carousel.Caption>
<h3>{item.title}</h3>
<p>
<br />
<br />
</p>
</Carousel.Caption>
</Carousel.Item>
);
})}
</Carousel>
{project.map((item, index) => (
<>
{console.log(item.title)}
<WebForm
show={showEdit}
setShow={setShowEdit}
formTitle="Edit Project Details"
titleName="item[][title]"
descriptionName="item[][description]"
projURLName="item[][URL]"
titleVal={item.title}
descriptionVal={item.description}
projURLVal={item.URL}
onChange={(e) => handleChange(e)}
onSubmit={submit}
btnValue="Done"
/>
<WebForm
show={showAdd}
setShow={setShowAdd}
formTitle="Add a New Project"
titleName="project[title]"
descriptionName="project[description]"
projURLName="project[URL]"
titleVal={project.title}
descriptionVal={project.description}
projURLVal={project.URL}
onChange={(e) => handleChange(e)}
onSubmit={submit}
btnValue="Add"
/>
</>
))}
<AppButton onClick={() => setShowAdd(true)} value={"Add"} />
</div>
);
};
export default ProjectGrid;
The API
const express = require("express");
const app = express();
const fileHandler = require("fs");
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.get("/projects", (req, res) => {
fileHandler.readFile("web_project.json", (err, data) => {
if (err) res.send("File not found. First post to create file.");
else res.send(`${data}`);
});
});
app.post("/projects", (req, res) => {
res.send();
fileHandler.readFile("web_project.json", function (err, data) {
if (err) res.send("File not found. First post to create file.");
else {
let json = JSON.parse(data);
json.push({
id: req.body.id,
title: req.body.title,
description: req.body.description,
URL: req.body.URL,
});
console.log(JSON.stringify(json));
fileHandler.writeFile("web_project.json", JSON.stringify(json), (err) => {
if (err) throw err;
});
}
});
});
app.put("/projects/:id", (req, res) => {
const idParams = req.params.id;
console.log(idParams);
fileHandler.readFile("web_project.json", (err, data) => {
if (err) res.send("File not found. First post to create file.");
else {
let project = JSON.parse(data);
//console.log(project);
for (let i = project.length - 1; i > -1; i--) {
console.log(project[i].id);
if (project[i].id === parseInt(idParams)) {
project = req.body;
console.log(req.body);
}
}
console.log(JSON.stringify(project));
fileHandler.writeFile(
"web_project.json",
JSON.stringify(project),
(err) => {
if (err) throw err;
res.send(JSON.stringify(project));
}
);
}
});
});
app.delete("/projects/:id", (req, res) => {
res.send(req.params.id);
const idParams = req.params.id;
console.log(idParams);
fileHandler.readFile("web_project.json", (err, data) => {
if (err) res.send("File not found. First post to create file.");
else {
let project = JSON.parse(data);
console.log(project);
for (let i = project.length - 1; i > -1; i--) {
console.log(project[i].id);
if (project[i].id === parseInt(idParams)) {
project.splice(i, 1);
}
}
console.log(JSON.stringify(project));
fileHandler.writeFile(
"web_project.json",
JSON.stringify(project),
(err) => {
if (err) throw err;
}
);
}
});
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
//localhost:8080/api/web-projects?id=4&title=Online-store&description=johnny&URL=something
And finally the JSON file
[
{
"id": 1,
"title": "React Game!",
"description": "Tic tac toe game created using Create React app.",
"URL": "https://protected-anchorage-65131.herokuapp.com/"
},
{
"id": 3,
"title": "Online Shop",
"description": "Online store for a decor and design company.",
"URL": "https://kondtoin.lsojdnf/kdkfsj"
},
{
"id": 2,
"title": "Online Portfolio",
"description": "Online portfolio created with HTML, CSS and JavaScript.",
"URL": "https://jc03ceige.github.io/JC_van_der_Merwe/"
}
]
CodePudding user response:
The problem is on React side.
In the submit method you're doing const data = new FormData(form.current);
.
But the value of form.current
will always be null since you're never using the ref that you defined with this line const form = useRef(null);
Instead of using a ref I think the following should work
const submit = (e) => {
e.preventDefault();
// const data = new FormData(form.current); remove
// The event contains the target that triggered the submit event
const data = new FormData(e.target);
console.log(project);
console.log(data);
fetch("/projects", {
method: "POST",
body: data,
})
.then((res) => res.json())
.then((json) => setProject(json.project));
console.log(project);
console.log(data);
};
CodePudding user response:
When you create an instance of FormData you need to append data to the formData object. Use this solution
const data = new FormData()
data.append("username", username)
// Go ahead and append all your variables
On your Nodejs server, if you're not sending a file along with your form, do not send form data content-type to the server. Use json instead