Related
I tried implementing a student management system using koa js for the back-end and react for the front-end. Below I have given the controller, model, route, and utils of my Koa backend. I used react js for the front end. I used the latest versions of dependencies for both the front end and the back end. The app is compiling successfully. But the problem is data is not saved in the MongoDBcompass. I didn't use MongoDB Atlas as the DB. I used the compass. I can't find any error. In the react app update course function is not working. But the rest of the code is implemented correctly. Once I checked the back end through the Postman. It was perfectly fine. POST, GET, PUT, and DELETE requests worked for the course function. after connecting the front end also worked properly.But when it comes to the student functionality it didn't fetch the data. After connecting the front end the issue came up. Can anyone please find out the issue?
dbConnect.js
const mongoose = require("mongoose");
const dbConnect = () => {
const dbConStr = process.env.MONGODB_URL;
mongoose.connect(dbConStr, () => {
console.log("Database connected");
});
};
module.exports = { dbConnect };
index.js
require("dotenv").config();
const Koa = require("koa");
const KoaRouter = require("koa-router");
const cors = require("#koa/cors");
const bodyParser = require("koa-bodyparser");
const json = require("koa-bodyparser");
const { dbConnect } = require("./utils/dbConnect");
const courseRoutes = require("./routes/course.routes");
const studentRoutes = require("./routes/student.routes");
const app = new Koa();
const router = new KoaRouter();
app.use(cors());
app.use(bodyParser());
app.use(json());
app.use(router.routes()).use(router.allowedMethods());
app.use(courseRoutes.routes());
app.use(studentRoutes.routes());
router.get("/", (ctx) => {
ctx.body = { message: "Student Management API" };
});
app.listen(9000, () => {
dbConnect();
console.log(`Server is up and running on http://localhost:9000`);
});
student.router.js
const KoaRouter = require("koa-router");
const {
addStudent,
getStudents,
updateStudent,
deleteStudent,
} = require("../controller/student.controller");
const router = new KoaRouter({ prefix: "/student" });
router.post("/add", addStudent);
router.delete("/:studentId", deleteStudent);
router.put("/:studentId", updateStudent);
router.get("/", getStudents);
module.exports = router;
course.router.js
const KoaRouter = require("koa-router");
const router = new KoaRouter({ prefix: "/course" });
const {
addCourse,
getCourses,
updateCourse,
deleteCourse,
} = require("../controller/course.controller");
router.post("/add", addCourse);
router.delete("/:courseId", deleteCourse);
router.put("/:courseId", updateCourse);
router.get("/", getCourses);
module.exports = router;
student.model.js
const mongoose = require("mongoose");
const StudentSchema = new mongoose.Schema({
name: { type: String, required: true },
nic: { type: String, required: true },
age: { type: Number, required: true },
courseId: { type: mongoose.Schema.Types.ObjectId, required: false, ref: "courses" },
});
const Student = mongoose.model("students", StudentSchema);
module.exports = Student;
course.model.js
const mongoose = require("mongoose");
const CourseSchema = new mongoose.Schema({
courseName: { type: String, required: true },
courseFee: { type: Number, required: true },
students: [{ type: mongoose.Schema.Types.ObjectId, required: false, ref: "students" }],
});
const Course = mongoose.model("courses", CourseSchema);
module.exports = Course;
student.controller.js
const Student = require("../models/student.model");
const Course = require("../models/course.model");
const addStudent = async (ctx) => {
try {
const { name, nic, age, courseId } = ctx.request.body;
const student
= await Student.create({
name,
nic,
age,
courseId,
});
await Course.findByIdAndUpdate(courseId, { $push: { students: student._id } });
return (ctx.body = student);
} catch (error) {
return (ctx.body = { message: error.message });
}
};
const getStudents = async (ctx) => {
try {
const students = await Student.find().populate({
path: "courseId",
select: "courseName courseFee",
});
return (ctx.body = students);
} catch (error) {
return (ctx.body = { message: error.message });
}
};
const updateStudent = async (ctx) => {
try {
const studentId = ctx.params.studentId;
const { name, nic, age, courseId } = ctx.request.body;
const student = await Student.findByIdAndUpdate(studentId, {
name,
nic,
age,
courseId,
});
await Course.findByIdAndUpdate(student.courseId, {
$pull: { students: studentId },
});
await Course.findByIdAndUpdate(courseId, {
$push: { students: studentId },
});
return (ctx.body = student);
} catch (error) {
return (ctx.body = { message: error.message });
}
};
const deleteStudent = async (ctx) => {
try {
const studentId = ctx.params.studentId;
const student = await Student.findById(studentId);
await Course.findByIdAndUpdate(student.courseId, { $pull: { students: studentId } });
await Student.findByIdAndDelete(studentId);
return (ctx.body = student);
} catch (error) {
return (ctx.body = { message: error.message });
}
};
module.exports = {
addStudent,
getStudents,
updateStudent,
deleteStudent,
};
course.controller.js
const Course = require("../models/course.model");
const addCourse = async (ctx) => {
try {
const { courseName, courseFee, students } = ctx.request.body;
const course = await Course.create({
courseName: courseName,
courseFee: courseFee,
students: students,
});
return (ctx.body = course);
} catch (error) {
return (ctx.body = { message: error.message });
}
};
const getCourses = async (ctx) => {
try {
const courses = await Course.find({}).populate({ path: "students", select: "name nic age" });
return (ctx.body = courses);
} catch (error) {
return (ctx.body = { message: error.message });
}
};
const updateCourse = async (ctx) => {
try {
const courseId = ctx.params.courseId;
const { courseName, courseFee, students } = ctx.request.body;
const course = await Course.findByIdAndUpdate(courseId, {
courseName: courseName,
courseFee: courseFee,
students: students,
});
return (ctx.body = course);
} catch (error) {
return (ctx.body = { message: error.message });
}
};
const deleteCourse = async (ctx) => {
try {
const courseId = ctx.params.courseId;
const course = await Course.findByIdAndDelete(courseId);
return (ctx.body = course);
} catch (error) {
return (ctx.body = { message: error.message });
}
};
module.exports = {
addCourse,
getCourses,
updateCourse,
deleteCourse,
};
React App
Course.js
import React, { useEffect, useState } from "react";
import axios from "axios";
const CoursePage = () => {
const [courseName, setCourseName] = useState("");
const [courseFee, setCourseFee] = useState("");
const [courses, setCourses] = useState([]);
const [isEditClick, setIsEditClick] = useState(false);
const [editId, setEditId] = useState("");
const [editName, setEditName] = useState("");
const [editFee, setEditFee] = useState("");
useEffect(() => {
axios.get(`${process.env.BASE_URL}/course/`).then((res) => {
setCourses(res.data);
});
}, []);
const saveData = (e) => {
e.preventDefault();
const courseObj = {
courseName,
courseFee,
};
axios
.post(`${process.env.BASE_URL}/course/add`, courseObj)
.then((res) => {
alert("Data added");
axios.get(`${process.env.BASE_URL}/course/`).then((res) => {
setCourses(res.data);
});
setCourseName("");
setCourseFee("");
})
.catch((err) => {
alert(err.message);
});
};
const updateData = (e) => {
e.preventDefault();
const courseObj = {
courseName: editName,
courseFee: editFee,
};
console.log(courseObj);
axios
.put(`${process.env.BASE_URL}/course/${editId}`, courseObj)
.then((res) => {
alert("Course Updated");
axios.get(`${process.env.BASE_URL}/course/`).then((res) => {
setCourses(res.data);
});
setIsEditClick(false);
})
.catch((err) => {
alert(err.message);
});
};
const deleteCourse = (e) => {
e.preventDefault();
axios
.delete(`${process.env.BASE_URL}/course/${e.target.id}`)
.then(() => {
axios.get(`${process.env.BASE_URL}/course/`).then((res) => {
setCourses(res.data);
});
})
.catch((err) => {
alert(err.message);
});
};
const onEditClick = (e) => {
e.preventDefault();
setEditId(e.target.id);
setIsEditClick(!isEditClick);
const course = courses.find((course) => course._id === e.target.id);
setEditName(course.courseName);
setEditFee(course.courseFee);
};
return (
<div>
<h1>Course Page</h1>
<div>
<input
type="text"
placeholder="Enter course name"
value={courseName}
style={{ margin: 5 }}
onChange={(e) => setCourseName(e.target.value)}
/>
<input
type="number"
placeholder="Enter course free"
value={courseFee}
style={{ margin: 5 }}
onChange={(e) => setCourseFee(e.target.value)}
/>
<button onClick={(e) => saveData(e)} style={{ margin: 5 }}>
Submit
</button>
<br />
<table>
<tr>
<th>Course Name</th>
<th>Course Fee</th>
<th>Actions</th>
</tr>
{courses &&
courses.length > 0 &&
courses.map((course, index) => (
<tr key={index}>
<td>
{isEditClick && course._id === editId ? (
<input
type="text"
value={editName}
onChange={(e) => setEditName(e.target.value)}
/>
) : (
course.courseName
)}
</td>
<td>
{isEditClick && course._id === editId ? (
<input
type="number"
value={editFee}
onChange={(e) => setEditFee(e.target.value)}
/>
) : (
course.courseFee
)}
</td>
<td>
<button id={course._id} onClick={(e) => onEditClick(e)}>
{isEditClick && course._id === editId ? "Cancel" : "Update"}
</button>
{isEditClick && course._id === editId && (
<button onClick={(e) => updateData(e)}>Save</button>
)}
<button id={course._id} onClick={(e) => deleteCourse(e)}>
Delete
</button>
</td>
</tr>
))}
</table>
</div>
</div>
);
};
export default CoursePage;
Home.js
import React from "react";
import { Link } from "react-router-dom";
const Home = () => {
return (
<div>
<Link to="student">
<button>Manage Students</button>
</Link>
<Link to="course">
<button>Manage Courses</button>
</Link>
</div>
);
};
export default Home;
Student.js
import React, { useEffect, useState } from "react";
import axios from "axios";
const StudentPage = () => {
const [name, setName] = useState("");
const [nic, setNIC] = useState("");
const [age, setAge] = useState("");
const [courseId, setCourseId] = useState("");
const [editStudentId, setEditStudentId] = useState("");
const [students, setStudents] = useState([]);
const [courses, setCourses] = useState([]);
const [isEditClick, setIsEditClick] = useState(false);
const [editName, setEditName] = useState("");
const [editNIC, setEditNIC] = useState("");
const [editAge, setEditAge] = useState("");
const [editCourseId, setEditCourseId] = useState("");
useEffect(() => {
axios.get(`${process.env.BASE_URL}/course/`).then((res) => {
setCourses(res.data);
});
axios.get(`${process.env.BASE_URL}/student/`).then((res) => {
setStudents(res.data);
});
}, []);
const addStudent = (e) => {
e.preventDefault();
const studentObj = {
name,
nic,
age,
courseId
}
axios
.post(`${process.env.BASE_URL}/student/add`, studentObj)
.then((res) => {
alert("Data added");
axios.get(`${process.env.BASE_URL}/student/`).then((res) => {
setStudents(res.data);
});
setName("");
setNIC("");
setAge("");
setCourseId("");
})
.catch((err) => {
alert(err.message);
});
};
const updateStudent = (e) => {
e.preventDefault();
const studentObj = {
name: editName,
nic: editNIC,
age: editAge,
courseId: editCourseId
};
console.log(studentObj);
axios
.put(`${process.env.BASE_URL}/student/${editStudentId}`, studentObj)
.then((res) => {
alert("Student Updated");
axios.get(`${process.env.BASE_URL}/student/`).then((res) => {
setStudents(res.data);
});
setIsEditClick(false);
})
.catch((err) => {
alert(err.message);
});
};
const deleteStudent = (e) => {
e.preventDefault();
axios
.delete(`${process.env.BASE_URL}/student/${e.target.id}`)
.then(() => {
axios.get(`${process.env.BASE_URL}/student/`).then((res) => {
setStudents(res.data);
});
})
.catch((err) => {
alert(err.message);
});
};
const onEditClick = (e) => {
e.preventDefault();
setEditStudentId(e.target.id);
setIsEditClick(!isEditClick);
const student = students.find((student) => {
student._id === e.target.id;
});
if (student) {
setEditName(student.name);
setEditAge(student.age);
setEditNIC(student.nic);
setIsEditClick(true);
}
};
return (
<div>
<h1>Student Page</h1>
<div style={{ marginBottom: 5 }}>
<input
type="text"
placeholder="Enter name"
value={name}
style={{ margin: 5 }}
onChange={(e) => setName(e.target.value)}
/>
<input
type="text"
placeholder="Enter nic"
value={nic}
style={{ margin: 5 }}
onChange={(e) => setNIC(e.target.value)}
/>
<input
type="number"
placeholder="Enter age"
value={age}
style={{ margin: 5 }}
onChange={(e) => setAge(e.target.value)}
/>
<select onChange={(e) => setCourseId(e.target.value)}>
{courses &&
courses.length > 0 &&
courses.map((course, index) => (
<option value={course._id} key={index}>
{course.courseName}
</option>
))}
</select>
<button onClick={(e) => addStudent(e)} style={{ margin: 5 }}>
Submit
</button>
</div>
<table>
<tr>
<th>Name</th>
<th>NIC</th>
<th>Age</th>
<th>Course</th>
<th>Actions</th>
</tr>
{students &&
students.length > 0 &&
students.map((student, index) => (
<tr key={index}>
<td>
{isEditClick && student._id === editId ? (
<input
type="text"
value={editName}
onChange={(e) => setEditName(e.target.value)}
/>
) : (
student.name
)}
</td>
<td>
{isEditClick && student._id === editId ? (
<input
type="text"
value={editNIC}
onChange={(e) => setEditNIC(e.target.value)}
/>
) : (
student.nic
)}
</td>
<td>
{isEditClick && student._id === editId ? (
<input
type="Number"
value={editNIC}
onChange={(e) => setEditAge(e.target.value)}
/>
) : (
student.age
)}
</td>
<td>{student.courseId && student.courseId.courseName}</td>
<td>
<button id={student._id} onClick={(e) => onEditClick(e)}>
{isEditClick && student._id === editStudentId ? "Cancel" : "Update"}
</button>
{isEditClick && student._id === editStudentId && (
<button onClick={(e) => updateStudent(e)}>Save</button>
)}
<button id={student._id} onClick={(e) => deleteStudent(e)}>
Delete
</button>
</td>
</tr>
))}
</table>
</div>
);
};
export default StudentPage;
App.js
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import CoursePage from "./pages/Course";
import Home from "./pages/Home";
import StudentPage from "./pages/Student";
const App = () => {
return (
<div>
<Router>
<Routes>
<Route path="/student" element={<StudentPage />} />
<Route path="/course" element={<CoursePage />} />
<Route path="/" element={<Home />} />
</Routes>
</Router>
</div>
);
};
export default App;
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));
I have a react app which allows creation of lists for logged in users (groceries, todo, etc) and stores data on firebase.firestore. It's still in development using localhost. If I create a new list, add items to it and immediately edit the item description, I get a TypeError and item description doesn't update in app or firestore. If I pick a different list to display, then click back to new list or refresh the browser before I edit the item description, everything works fine. It just doesn't work if I specifically create new list, add items to it, and immediately try to edit the item description, even though I check firestore before submitting change and the new list and new items are shown to exist.
Any idea why the await checkDoc in the handleSubmit isn't working unless I refresh app in browser first?
I included firestore screenshots before and after app refresh in case it matters. They seem identical to me.
Github repo is branch edit-item
github repo
error showing in console when I update item description without refreshing:
TypeError: u.indexOf is not a function
at Function.e.ot (prebuilt-67479dbf-318e5a2c.js:878)
at Bs (prebuilt-67479dbf-318e5a2c.js:14560)
at e.doc (prebuilt-67479dbf-318e5a2c.js:18281)
at handleSubmit (EditItem.js:32)
at HTMLUnknownElement.callCallback (react-dom.development.js:3945)
at Object.invokeGuardedCallbackDev (react-dom.development.js:3994)
at invokeGuardedCallback (react-dom.development.js:4056)
at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:4070)
at executeDispatch (react-dom.development.js:8243)
at processDispatchQueueItemsInOrder (react-dom.development.js:8275)
at processDispatchQueue (react-dom.development.js:8288)
at dispatchEventsForPlugins (react-dom.development.js:8299)
at react-dom.development.js:8508
at batchedEventUpdates$1 (react-dom.development.js:22396)
at batchedEventUpdates (react-dom.development.js:3745)
at dispatchEventForPluginEventSystem (react-dom.development.js:8507)
at attemptToDispatchEvent (react-dom.development.js:6005)
at dispatchEvent (react-dom.development.js:5924)
at unstable_runWithPriority (scheduler.development.js:646)
at runWithPriority$1 (react-dom.development.js:11276)
at discreteUpdates$1 (react-dom.development.js:22413)
at discreteUpdates (react-dom.development.js:3756)
at dispatchDiscreteEvent (react-dom.development.js:5889)
EditItem componant with handleSubmit function giving error unless I refresh first:
import React, { useState } from 'react';
import { db } from '../../hooks/useAuth';
import {
Button,
Input,
Center,
FormControl,
Flex,
Heading,
} from '#chakra-ui/react';
const EditItem = ({
user,
items,
setItems,
setEditItem,
currentList,
editItem,
themeObj,
}) => {
const checkDoc = db.collection('users').doc(user.uid);
const [newDesc, setNewDesc] = useState('');
const handleSubmit = async e => {
e.preventDefault();
try {
console.log(editItem);
console.log(newDesc);
console.log(currentList);
console.log(editItem.id);
await checkDoc
.collection(currentList)
.doc(editItem.id)
.update({ desc: newDesc });
const editedList = items.map(item =>
item.id === editItem.id ? { ...item, desc: newDesc } : item
);
setItems(editedList);
console.log(editedList);
setNewDesc(`${editItem.desc}`);
setEditItem(null);
} catch (err) {
console.log(err);
} finally {
}
};
return (
<Flex w="100%" grow="1" direction="column" p={6}>
<Center
mb="1rem"
borderRadius="lg"
p={3}
bg={themeObj.bg}
color={themeObj.color}
>
<Heading size="md">Edit Item Description</Heading>
</Center>
<Center
mb="1rem"
borderRadius="lg"
p={3}
bg={themeObj.bgItem}
color={themeObj.colorItem}
>
{newDesc.length ? newDesc : editItem.desc}
</Center>
<form
label="New Item Description"
onSubmit={handleSubmit}
style={{ width: '100%' }}
>
<FormControl>
<Input
required
variant="outline"
autoFocus
// ref={inputRef}
type="text"
id="newDesc"
placeholder="New Item Description"
required
value={newDesc}
onChange={e => setNewDesc(e.target.value)}
/>
<Button
variant="solid"
mt={4}
type="submit"
aria-label="Rename List"
color="white"
_hover={{
background: `${themeObj.checkScheme}`,
}}
bg="black"
>
Update
</Button>
<Button
variant="solid"
mt={4}
type="button"
onClick={() => {
setEditItem(null);
setNewDesc(`${editItem.desc}`);
}}
aria-label="cancel"
color="white"
_hover={{
background: `${themeObj.checkScheme}`,
}}
bg="red"
>
Cancel
</Button>
</FormControl>
</form>
</Flex>
);
};
export default EditItem;
Dashboard component (parent to EditItem):
import React, { useState, useEffect } from 'react';
import { InputGroup, Stack, Flex } from '#chakra-ui/react';
import firebase from 'firebase/app';
import EditItem from '../iList/EditItem';
import Loader from '../iList/Loader';
import AddItem from '../iList/AddItem';
import SearchItem from '../iList/SearchItem';
import Content from '../iList/Content';
import Footer from '../iList/Footer';
import { useAuth, db } from '../../hooks/useAuth';
import 'firebase/firestore';
const Dashboard = ({
setAppTheme,
loaderLoading,
setLoaderLoading,
setIsLoading,
isLoading,
fetchError,
setFetchError,
themeObj,
currentList,
setCurrentList,
}) => {
console.log(currentList);
const { user } = useAuth();
const [items, setItems] = useState([]);
const [search, setSearch] = useState('');
const [editItem, setEditItem] = useState(null);
const [newItem, setNewItem] = useState('');
const checkDoc = db.collection('users').doc(user.uid);
const itemsCollection = db
.collection('users')
.doc(user.uid)
.collection(currentList);
useEffect(() => {
checkIfInitialized();
const getUserPrefs = async () => {
try {
const userList = await checkDoc.get();
setCurrentList(userList.data().currentlist);
setAppTheme(userList.data().currenttheme);
setFetchError(null);
} catch (err) {
setFetchError(err.message);
console.log(err.message);
} finally {
setLoaderLoading(false);
setIsLoading(false);
}
};
getUserPrefs();
}, []);
useEffect(() => {
const getItems = async () => {
try {
const data = await itemsCollection.get();
const listItems = data.docs.map(doc => ({
...doc.data(),
id: doc.id,
}));
setItems(listItems);
setFetchError(null);
} catch (err) {
setFetchError(err.message);
} finally {
setLoaderLoading(false);
}
};
getItems();
}, [currentList]);
const addItem = async item => {
const id = items.length ? Number(items[items.length - 1].id) + 1 : 1;
console.log(id);
const newItemDate = new Date();
const dateStr = `${
newItemDate.getMonth() + 1
}/${newItemDate.getDate()}/${newItemDate.getFullYear()}`;
const myNewItem = {
id: id,
checked: false,
desc: item,
date: dateStr,
};
const listItems = [...items, myNewItem];
setItems(listItems);
const addedDoc = db
.collection('users')
.doc(user.uid)
.collection(currentList)
.doc(`${myNewItem.id}`);
await addedDoc
.set({ desc: myNewItem.desc, checked: false, date: dateStr })
.then(() => {
console.log('Document successfully written!');
})
.catch(error => {
console.error('Error writing document: ', error);
});
};
const handleCheck = async id => {
const listItems = items.map(item =>
item.id === id ? { ...item, checked: !item.checked } : item
);
setItems(listItems);
const myItem = items.filter(item => item.id === id);
console.log(myItem);
const updatedDoc = db
.collection('users')
.doc(user.uid)
.collection(currentList)
.doc(`${id}`);
console.log('here');
await updatedDoc
.update({
checked: !myItem[0].checked,
})
.then(() => {
console.log('Document successfully updated!');
})
.catch(error => {
// The document probably doesn't exist.
console.error('Error updating document: ', error);
});
};
const handleDelete = async id => {
const listItems = items.filter(item => item.id !== id);
setItems(listItems);
const deletedDoc = db
.collection('users')
.doc(user.uid)
.collection(currentList)
.doc(`${id}`);
await deletedDoc
.delete()
.then(() => {
console.log('Document successfully deleted!');
})
.catch(error => {
console.error('Error removing document: ', error);
});
};
const handleSubmit = e => {
e.preventDefault();
if (!newItem) return;
addItem(newItem);
setNewItem('');
};
const checkIfInitialized = () => {
const docRef = db.collection('users').doc(user.uid);
docRef
.get()
.then(doc => {
if (doc.exists) {
console.log('Document data:', doc.data());
} else {
// doc.data() will be undefined in this case
console.log('No such document!');
initializeUserDb();
}
})
.catch(error => {
console.log('Error getting document:', error);
});
};
const initializeUserDb = async () => {
const firstEntry = db.collection('users').doc(user.uid);
await firstEntry
.set({
currentlist: currentList,
mylists: firebase.firestore.FieldValue.arrayUnion('My List'),
currenttheme: 'default',
email: user.email,
})
.then(() => {
console.log('currentlist successfully written!');
})
.catch(error => {
console.error('Error writing document: ', error);
});
};
return (
<>
{editItem && (
<EditItem
items={items}
setItems={setItems}
currentList={currentList}
user={user}
editItem={editItem}
setEditItem={setEditItem}
themeObj={themeObj}
/>
)}
{!editItem && (
<>
<Stack mb={3} w="100%" p={3}>
<InputGroup>
<AddItem
themeObj={themeObj}
newItem={newItem}
setNewItem={setNewItem}
handleSubmit={handleSubmit}
/>
</InputGroup>
<InputGroup>
<SearchItem
themeObj={themeObj}
search={search}
setSearch={setSearch}
/>
</InputGroup>
</Stack>
<Flex
w="100%"
flexDirection="column"
flexGrow="1"
justifyContent="flex-start"
align-items="center"
overflowY="auto"
>
{(isLoading || loaderLoading) && <Loader />}
{fetchError && (
<p style={{ color: 'red' }}>{`Error: ${fetchError}`}</p>
)}
{!fetchError && !loaderLoading && !isLoading && (
<Content
setEditItem={setEditItem}
themeObj={themeObj}
items={items.filter(item =>
item.desc.toLowerCase().includes(search.toLowerCase())
)}
handleDelete={handleDelete}
handleCheck={handleCheck}
/>
)}
</Flex>
</>
)}
<Footer bg={themeObj.bg} color={themeObj.color} length={items.length} />
</>
);
};
export default Dashboard;
firestore screenshot before refreshing app (I just created "test list" and two items and updating item desc doesn't work)
firestore screenshot after refreshing app (updating item description works)
I figured it out...
The id of newly created item which I am using to update the doc is a number. But the id on firestore is a string, not a number.
changing the handleSubmit callback from .doc(editItem.id) to this fixed it:
.doc(`${editItem.id}`)
I am trying to update user information (name, email, password) but getting two errors.
If I try to enter & update name or Email (DATA DOES NOT GET UPDATED) I get 404 error :
Request:
Response:
But, if I try to enter and update Password it gets UPDATED (as I have to enter new password while logging in again) but it shows these error's immediately after submit button:
I have tried updating user data by putting data directly through thunder client & it's getting updated:
Here is my source code:
FRONTEND
ProfileScreen.js
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import ErrorMessage from "../../components/ErrorMessage/ErrorMessage";
import Message from "../../components/Message/Message";
import Loader from "../../components/Loader/Loader";
import {
getUserDetails,
updateUserProfile,
} from "../../redux/actions/userActions";
import "./ProfileScreen.scss";
import { USER_UPDATE_PROFILE_RESET } from "../../redux/constants/userConstants";
const ProfileScreen = ({ location, history }) => {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [message, setMessage] = useState(null);
const regex =
/^(?=.*[A-Za-z])(?=.*\d)(?=.*[#$!%*#?&])[A-Za-z\d#$!%*#?&]{8,}$/;
const dispatch = useDispatch();
const userDetails = useSelector((state) => state.userDetails);
const { loading, error, user } = userDetails;
const userLogin = useSelector((state) => state.userLogin);
const { userInfo } = userLogin;
const userUpdateProfile = useSelector((state) => state.userUpdateProfile);
const { success } = userUpdateProfile;
useEffect(() => {
if (!userInfo) {
history.push("/login");
} else {
if (!user.name || !user || success) {
dispatch({ type: USER_UPDATE_PROFILE_RESET });
dispatch(getUserDetails("profile"));
} else {
setName(user.name);
setEmail(user.email);
}
}
}, [history, userInfo, dispatch, user, success]);
const passwordHandler = (e) => {
e.preventDefault();
!regex.test(password)
? setMessage(
"Password must contain atleast 8 characters & one alphabet, number & special character"
)
: password !== confirmPassword
? setMessage("Passwords do not match!")
: dispatch(updateUserProfile({ id: user._id, password }));
};
const enameHandler = (e) => {
e.preventDefault();
dispatch(updateUserProfile({ id: user._id, name, email, password }));
};
return (
<>
<div className="profile-container">
<div className="profile">
{message && <ErrorMessage error={message} />}
{error && <ErrorMessage error={error} />}
{success && <Message success={"Profile Updated"} />}
<div className="profile-form">
<h2>User Profile</h2>
{loading ? (
<Loader />
) : (
<div>
<form onSubmit={enameHandler}>
<div className="profile-form-items">
<h3> Update Name or Email</h3>
<input
className="profile-input"
type="name"
placeholder="New Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
className="profile-input"
type="email"
placeholder="New Email address"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button type="submit" value="submit">
Update
</button>
</div>
</form>
<form onSubmit={passwordHandler}>
<div className="profile-form-items">
<h3>Update Password</h3>
<input
className="profile-input"
type="password"
placeholder="New Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<input
className="profile-input"
type="password"
placeholder="Confirm New Password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
<button type="submit" value="submit">
Update
</button>
</div>
</form>
</div>
)}
</div>
</div>
</>
);
};
export default ProfileScreen;
userAction.js
import axios from "axios";
import {
USER_DETAILS_FAIL,
USER_DETAILS_REQUEST,
USER_DETAILS_SUCCESS,
USER_LOGIN_FAIL,
USER_LOGIN_REQUEST,
USER_LOGIN_SUCCESS,
USER_LOGOUT,
USER_REGISTER_FAIL,
USER_REGISTER_REQUEST,
USER_REGISTER_SUCCESS,
USER_UPDATE_PROFILE_FAIL,
USER_UPDATE_PROFILE_REQUEST,
USER_UPDATE_PROFILE_SUCCESS,
} from "../constants/userConstants";
export const login = (email, password) => async (dispatch) => {
try {
dispatch({
type: USER_LOGIN_REQUEST,
});
const config = {
headers: {
"Content-Type": "application/json",
},
};
const { data } = await axios.post(
"/api/users/login",
{ email, password },
config
);
dispatch({
type: USER_LOGIN_SUCCESS,
payload: data,
});
localStorage.setItem("userInfo", JSON.stringify(data));
} catch (error) {
dispatch({
type: USER_LOGIN_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
export const logout = () => (dispatch) => {
localStorage.removeItem("userInfo");
dispatch({
type: USER_LOGOUT,
});
};
export const register = (name, email, password) => async (dispatch) => {
try {
dispatch({
type: USER_REGISTER_REQUEST,
});
const config = {
headers: {
"Content-Type": "application/json",
},
};
const { data } = await axios.post(
"/api/users",
{ name, email, password },
config
);
dispatch({
type: USER_REGISTER_SUCCESS,
payload: data,
});
dispatch({
type: USER_LOGIN_SUCCESS,
payload: data,
});
localStorage.setItem("userInfo", JSON.stringify(data));
} catch (error) {
dispatch({
type: USER_REGISTER_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
export const getUserDetails = (id) => async (dispatch, getState) => {
try {
dispatch({
type: USER_DETAILS_REQUEST,
});
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${userInfo.token}`,
},
};
const { data } = await axios.get(`/api/users/${id}`, config);
dispatch({
type: USER_DETAILS_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: USER_DETAILS_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
export const updateUserProfile = (user) => async (dispatch, getState) => {
try {
dispatch({
type: USER_UPDATE_PROFILE_REQUEST,
});
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${userInfo.token}`,
},
};
console.log("UPDATE Action called");
const { data } = await axios.put(`/api/users/profile`, user, config);
dispatch({
type: USER_UPDATE_PROFILE_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: USER_UPDATE_PROFILE_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
userReducer.js
import {
USER_UPDATE_PROFILE_FAIL,
USER_UPDATE_PROFILE_REQUEST,
USER_UPDATE_PROFILE_SUCCESS,
} from "../constants/userConstants";
export const userUpdateProfileReducer = (state = {}, action) => {
switch (action.type) {
case USER_UPDATE_PROFILE_REQUEST:
return { loading: true };
case USER_UPDATE_PROFILE_SUCCESS:
return { loading: false, success: true, userInfo: action.payload };
case USER_UPDATE_PROFILE_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
store.js
import { createStore, combineReducers, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
// reducers
import {
userLoginReducer,
userRegisterReducer,
userDetailsReducer,
userUpdateProfileReducer,
} from "./reducers/userReducers";
const reducer = combineReducers({
userLogin: userLoginReducer,
userRegister: userRegisterReducer,
userDetails: userDetailsReducer,
userUpdateProfile: userUpdateProfileReducer,
});
const userInfoFromStorage = localStorage.getItem("userInfo")
? JSON.parse(localStorage.getItem("userInfo"))
: null;
const initialState = {
userLogin: { userInfo: userInfoFromStorage },
};
const middleware = [thunk];
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
BACKEND
userRoutes.js
const express = require("express");
const {
authUser,
getUserProfile,
registerUser,
updateUserProfile,
} = require("../controllers/userController");
const protect = require("../middleware/authMiddleware");
const router = express.Router();
router.route("/").post(registerUser);
router.post("/login", authUser);
router
.route("/profile")
.get(protect, getUserProfile)
.put(protect, updateUserProfile);
module.exports = router;
userController.js
// #description: Update user profile
// #route: PUT /api/users/profile
// #access: Private
exports.updateUserProfile = async (req, res, next) => {
try {
const user = await User.findById(req.user._id);
if (user) {
user.name = req.body.name || user.name;
user.email = req.body.email || user.email;
if (req.body.password) {
user.password = req.body.password;
}
const updatedUser = await user.save();
res.json({
_id: updatedUser._id,
name: updatedUser.name,
email: updatedUser.email,
isAdmin: updatedUser.isAdmin,
token: generateToken(updatedUser._id),
});
}
} catch (error) {
error = new Error("User not found");
error.status = 404;
next(error);
}
};
authMiddleware.js
const jwt = require("jsonwebtoken");
const User = require("../models/userModel");
const protect = async (req, res, next) => {
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith("Bearer")
) {
try {
token = req.headers.authorization.split(" ")[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = await User.findById(decoded.id).select("-password");
next();
} catch (error) {
error = new Error("Not Authorized!!");
error.status = 401;
next(error);
}
}
if (!token) {
const error = new Error("Not Authorized!!, No Token!!");
error.status = 401;
next(error);
}
};
module.exports = protect;
The error is (most probably) in
const user = await User.findById(req.user._id);
check if you are getting the "req.user._id" correctly. And also check your authMiddlwere if there is any error in getting req.user._id .
Hi I am new to redux and authentication. I am creating a react/redux app where a user can login and be able to add a story by submitting a form. I am able to login but when I get to the story creation page, I click submit and I get a POST http://localhost:3000/api/v1/stories 401 (Unauthorized) error.
I am logging in using an API that gives a token on login. I then save the username and token to sessionstorage. But how would I fix this error?
App.js
import './App.scss';
import Login from './components/Login';
import { Router, Switch, Route, NavLink } from 'react-router-dom';
import PrivateRoute from './utils/PrivateRoute';
import CreateStory from './components/CreateStory';
import history from './utils/history';
function App() {
return (
<div className="App">
<Router history={history}>
<Switch>
<Route exact path="/" component={Login} />
<PrivateRoute path="/user" component={CreateStory}/>
</Switch>
</Router>
</div>
);
}
export default App;
PrivateRoute.js
import { useSelector } from 'react-redux'
// handle the private routes
function PrivateRoute({ component: Component, ...rest }) {
const getToken = useSelector((state)=> state.loginReducer.token)
console.log(getToken)
return (
<Route
{...rest}
render={(props) => getToken ? <Component {...props} /> : <Redirect to={{ pathname: '/', state: { from: props.location } }} />}
/>
)
}
export default PrivateRoute;
CreateStory.js
import React, { useState } from 'react'
import { createStory } from '../redux/actions'
import { useDispatch } from "react-redux";
const CreateStory = () => {
const [summary, setSummary] = useState("");
const [description, setDescription] = useState("");
const [type, setType] = useState("");
const [complexity, setcomplexity] = useState("");
const usedispatch = useDispatch();
const userCreateStory = (summary, description, type, complexity) => usedispatch(createStory({
'summary': summary,
'description': description,
'type': type,
'complexity': complexity
}));
const handleSummaryChange = e => {
setSummary(e.target.value)
}
const handleDescriptionChange = e => {
setDescription(e.target.value)
}
const handleTypeChange = e => {
setType(e.target.value)
}
const handleComplexityChange = e => {
setcomplexity(e.target.value)
}
const handleSubmit = e => {
e.preventDefault();
userCreateStory('a','b','c','d')
// setTimeout(()=> history.push("/user"), 1000 );
}
return (
<div>
<form className='create-story-form'>
<label for="summary">Summary:</label>
<input name="summary" type='text' onChange={handleSummaryChange}/>
<label for="desc">Description:</label>
<textarea name="desc" type='text' onChange={handleDescriptionChange}/>
<label for="type">Type:</label>
<select name="type">
<option value="enhancement">Enchancement</option>
<option value="bugfix">Bugfix</option>
<option value="development">Development</option>
<option value="qa">QA</option>
</select>
<label for="complexity">Complexity:</label>
<select name="complexity">
<option value="Low">Low</option>
<option value="Mid">Mid</option>
<option value="High">High</option>
</select>
<label for="time">Estimated time for completion:</label>
<input name="time" type='text' />
<label for="cost">Cost:</label>
<input name="cost" type='number' />
<button onClick={handleSubmit}>Submit</button>
</form>
</div>
)
}
export default CreateStory;
Login.js
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { login, roleChange } from '../redux/actions' //OUR ACTIONS
import { useSelector } from 'react-redux'
import history from '../utils/history';
import { withRouter } from 'react-router-dom';
const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const usedispatch = useDispatch();
const userLogin = (email, password) => usedispatch(login({'email': email, 'password': password }));
const switchToAdmin = () => usedispatch(roleChange('admin'));
const switchToUser = () => usedispatch(roleChange('user'));
const currentRole = useSelector((state)=> state.loginReducer.role)
const handleRoleChange = e => {
e.preventDefault();
if(currentRole === 'user')
switchToAdmin();
else if(currentRole === 'admin' )
switchToUser()
}
const handleEmailChange = e => {
setEmail(e.target.value)
}
const handlePasswordChange = e => {
setPassword(e.target.value)
}
const handleSubmit = e => {
e.preventDefault();
userLogin(email, password)
setTimeout(()=> history.push("/user"), 1000 );
}
const disabled = () => {
return email === "" || password === ""
}
return (
<div>
<form className='login-form'>
<input type='email' name='email' placeholder='Email' onChange={handleEmailChange}/>
<input type='password' name='password' placeholder='Password' onChange={handlePasswordChange}/>
<button type='submit' disabled={disabled()} onClick={handleSubmit}>Login</button>
</form>
<button onClick={handleRoleChange}>Switch to {currentRole === 'user' ? 'admin' : 'user'}</button>
</div>
)
}
export default withRouter(Login);
actionTypes.js
export const SET_LOGIN_STATE = "SET_LOGIN_STATE"
export const SET_ROLE_STATE = "SET_ROLE_STATE"
export const CREATE_STORY = "CREATE_STORY"
initialState.js:
import { getToken } from '../utils/Common'
export const initialState = {
isLoggedIn: false,
userId: '',
role: 'user',
token: getToken,
data: '',
};
reducers.js
import { initialState } from './initialState';
import * as t from './actionTypes';
export const loginReducer = (state = initialState, action) => {
switch (action.type) {
case t.SET_ROLE_STATE:
return {
...state,
role: action.payload,
};
case t.SET_LOGIN_STATE:
return {
...state,
...action.payload, // this is what we expect to get back from API call and login page input
isLoggedIn: true, // we set this as true on login
};
default:
return state;
}
};
export const storyReducer = (state = initialState, action) => {
switch (action.type) {
case t.CREATE_STORY:
return {
...state,
role: action.payload,
};
default:
return state;
}
}
actions.js:
import * as t from './actionTypes';
import { setUserSession } from '../utils/Common';
// this is what our action should look like which dispatches the "payload" to reducer
const setLoginState = (loginData) => {
return {
type: t.SET_LOGIN_STATE,
payload: loginData, //{ ...json, userId: email }
};
};
const setStoryState = (storyData) => {
return {
type: t.CREATE_STORY,
payload: storyData,
};
};
export const login = (loginInput) => { //our login action
const { email, password } = loginInput;
return (dispatch) => { // don't forget to use dispatch here!
return fetch('http://localhost:3000/api/v1/signin', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(loginInput),
})
.then((response) => response.json()) //json will be the response body
.then((json) => {
// if (json.msg === 'success') { // response success checking logic could differ
// console.log(json)
dispatch(setLoginState({ ...json, userId: email })); // our action is called here with object as parameter, this is our payload
//we appended json object to our state
// } else {
// alert('Login Failed', 'Email or Password is incorrect');
// }
setUserSession(json.token, json.lastName)
})
.catch((err) => {
alert('Login Failed', 'Some error occured, please retry');
console.log(err);
});
};
};
export const roleChange = role => {
return {
type: t.SET_ROLE_STATE,
payload: role
};
}
/**
* story input:
{
"summary": "string",
"description": "string",
"type": "string",
"complexity": "string"
}
*/
export const createStory = storyInput => {
const { summary, description, type, complexity } = storyInput;
return (dispatch) => { // don't forget to use dispatch here!
return fetch('http://localhost:3000/api/v1/stories', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(storyInput),
})
.then((response) => response.json()) //json will be the response body
.then((json) => {
// if (json.msg === 'success') { // response success checking logic could differ
console.log(json)
// dispatch(setStoryState({ // our action is called here with object as parameter, this is our payload
// summary: summary,
// description: description,
// type: type,
// complexity: complexity
// })); // our action is called here
// } else {
// alert('Login Failed', 'Email or Password is incorrect');
// }
})
.catch((err) => {
alert('Some error occured, please retry');
console.log(err);
});
};
}
Common.js
// return the user data from the session storage
export const getUser = () => {
const userStr = sessionStorage.getItem('user');
if (userStr) return JSON.parse(userStr);
else return null;
}
// return the token from the session storage
export const getToken = () => {
return sessionStorage.getItem('token') || null;
}
// remove the token and user from the session storage
export const removeUserSession = () => {
sessionStorage.removeItem('token');
sessionStorage.removeItem('user');
}
// set the token and user from the session storage
export const setUserSession = (token, user) => {
sessionStorage.setItem('token', token);
sessionStorage.setItem('user', JSON.stringify(user));
}
You'll have to pass the auth token from the sessionStorage to the header of API you are posting your story to :-
const token = sessionStorage.getItem('token'); //Add this line
return fetch('http://localhost:3000/api/v1/stories', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${token}` //Add this line
},
body: JSON.stringify(storyInput),
})
I have this React component that I use to update business entities. It basically fetches by ID on componentDidMount and sends a put request when the form is submitted. I would like to refactor this to a hook based component.
Here is the code before
import React from "react";
import axios from "axios";
//Api Helper Methods
const API_HOST = "https://api.example.com";
const get = (endPoint) =>
axios
.get(`${API_HOST}/${endPoint}`)
.then((response) => response.data);
export const put = (endPoint, payload, id) =>
axios
.put(`${API_HOST}/${endPoint}/${id}`, payload)
.then((response) => response.data);
//React route (uses React Router)
const END_POINT = `users`;
class Entity extends React.Component {
state = { entity: {}, fetching: true };
getEntity = async () => {
const { id } = this.props.match.params;
this.setState({ fetching: true });
const entity = await get(`${END_POINT}/${id}`);
this.setState({ entity, fetching: false });
};
onChange = (key, value) =>
this.setState({ entity: { ...this.state.entity, [key]: value } });
componentDidMount() {
this.getEntity();
}
onSubmit = async (e) => {
e.preventDefault();
let { entity } = this.state;
let { match } = this.props;
await put(END_POINT, entity, match.params.id);
};
render() {
const { entity, fetching } = this.state;
if (fetching) {
return <p>loading...</p>;
}
return (
<form onSubmit={this.onSubmit}>
<label htmlFor="name">name</label>
<input
value={entity["name"]}
onChange={(e) => this.onChange("name", e.target.value)}
/>
<button type="submit">submit</button>
</form>
);
}
}
export default Entity;
This is what I have so far for the code after. Next step would be to extract custom hook.
const END_POINT = `users`;
export default function Entity({ match }) {
const [entity, setEntity] = useState({ name: "" });
const [fetching, setFetching] = useState( true );
const { id } = match.params;
const onChange = (key, value) => setEntity({ ...entity, [key]: value });
useEffect(() => {
const fetchEntity = async () => {
const entity = await get(`${END_POINT}/${id}`);
setEntity(entity);
setFetching(false);
};
fetchEntity();
}, [id]);
const onSubmit = async (e) => {
e.preventDefault();
await put(END_POINT, entity, id);
};
if (fetching) {
return <p>loading...</p>;
}
return (
<form onSubmit={onSubmit}>
<label htmlFor="name">name</label>
<input
value={entity["name"]}
onChange={(e) => onChange("name", e.target.value)}
/>
<button type="submit">submit</button>
</form>
);
}
I haven't tested this but this should be close to what you want with a custom hook for your entity function.
import React, { useEffect, useState } from 'react';
const API_HOST = "https://api.example.com";
const END_POINT = `users`;
function useEntity(entityID) {
const [entity, setEntity] = useState({})
useEffect(() => {
(async () => {
await fetch(`${API_HOST}/${END_POINT}/${props.match.params}`)
.then(async res => await res.json())
.then(result => setEntity(result));
})();
}, [])
return entity
}
export default function Entity(props) {
const { id } = props.match;
const entity = useEntity(id);
const onSubmit = async () => await fetch(`${API_HOST}/${END_POINT}/${id}`, {method: 'PUT', body: entity})
if (!entity) {
return <p>loading...</p>;
}
return (
<form onSubmit={onSubmit}>
<label htmlFor="name">name</label>
<input
value={entity["name"]}
onChange={(e) => setEntity({ ...entity, name: e.target.value})}
/>
<button type="submit">submit</button>
</form>
)
}
Thanks for the help Harben, I got it working like this.
import React, {useEffect, useState} from "react";
import axios from "axios";
//Api Helper Methods
const API_HOST = "https://api.example.com";
const get = (endPoint) =>
axios.get(`${API_HOST}/${endPoint}`).then((response) => response.data);
export const put = (endPoint, payload, id) =>
axios
.put(`${API_HOST}/${endPoint}/${id}`, payload)
.then((response) => response.data);
const END_POINT = `users`;
const useEntity = (entityId) => {
const [entity, setEntity] = useState({ name: "" });
const [fetching, setFetching] = useState(true);
useEffect(() => {
(async () => {
const entity = await get(`${END_POINT}/${entityId}`);
setEntity(entity);
setFetching(false);
})();
}, [entityId]);
return [entity, fetching, setEntity];
};
//React route (uses React Router)
export default function Entity({ match }) {
const { id } = match.params;
const [entity, fetching, setEntity] = useEntity(id);
const onChange = (key, value) => setEntity({ ...entity, [key]: value });
const onSubmit = async (e) => {
e.preventDefault();
await put(END_POINT, entity, id);
};
if (fetching) {
return <p>loading...</p>;
}
return (
<form onSubmit={onSubmit}>
<label htmlFor="name">name</label>
<input
value={entity["name"]}
onChange={(e) => onChange("name", e.target.value)}
/>
<button type="submit">submit</button>
</form>
);
}