Could not make POST request to add comments to my backend server - javascript

I am new to express and router. When I try to add a comment to the database, the console returned the error ("Post 400 Bad request" and "Uncaught in Promise"). I've tried many solutions but it doesn't work. I think it is to do with my routing.
Below is my profilesRouter.js in the backend folder:
const express = require("express");
const router = express.Router();
class profilesRouter {
constructor(controller) {
this.controller = controller;
}
routes() {
router.get("/", this.controller.getAll.bind(this.controller));
router.get(
"/:profileId/comments",
this.controller.getComments.bind(this.controller)
);
router.post(
"/:profileId/comments",
this.controller.addComment.bind(this.controller)
);
return router;
}
}
module.exports = profilesRouter;
Here is my profilesController.js in the backend folder.
const BaseController = require("./baseController");
class profilesController extends BaseController {
constructor(model) {
super(model);
}
async getComments(req, res) {
const { profileId } = req.params;
try {
const comments = await this.models.comment.findByPk(profileId);
return res.json(comments);
} catch (err) {
return res.status(400).json({ error: true, msg: err });
}
}
async addComment(req, res) {
try {
const comment = { ...req.body };
const newComment = await this.models.comment.create(comment);
return res.json(newComment);
} catch (err) {
return res.status(400).json({ error: true, msg: err });
}
}
}
module.exports = profilesController;
On the other hand, for my frontend folder:
Here is my App.js:
import React from "react";
import { useState, useEffect } from "react";
import Single from "./Single";
import { Routes, Route } from "react-router-dom";
export default function App() {
const [profiles, setprofiles] = useState([]);
const getInitialData = async () => {
let initialAPICall = await axios.get(
`${process.env.REACT_APP_API_SERVER}/profiles`
);
setprofiles(initialAPICall.data);
};
useEffect(() => {
getInitialData();
}, []);
return (
<div className="App">
<Routes>
<Route exact path="/" element={<Home />}></Route>
<Route
exact
path="/profiles"
element={<Card profiles={profiles} />}
></Route>
<Route
path="/profiles/:profileIndex"
element={<Single profiles={profiles} />}
></Route>
</Routes>
</div>
);
}
Upon clicking on individual profiles, it will bring me to Single.js
import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import axios from "axios";
import { BACKEND_URL } from "../src/constant";
const Single = ({ profiles }) => {
const [comments, setComments] = useState();
const [commentContent, setCommentContent] = useState("");
const { profileIndex } = useParams();
const profile = profiles[profileIndex];
console.log(profile);
useEffect(() => {
// If there is a profiles.id, retrieve the profile data
if (profile.id) {
axios
.get(`${BACKEND_URL}/profiles/${profile.id}/comments`)
.then((response) => {
setComments(response.data);
});
}
// Only run this effect on change to profiles.id
}, [profile.id]);
console.log(profile.id);
if (!profile) {
return "No Profile";
}
const handleChange = (event) => {
setCommentContent(event.target.value);
};
const handleSubmit = (event) => {
// Prevent default form redirect on submission
event.preventDefault();
// Send request to create new comment in backend
axios
.post(
`${BACKEND_URL}/profiles/${profile.id}/comments`,
{
content: commentContent,
}
)
.then((res) => {
// Clear form state
setCommentContent("");
// Refresh local comment list
return axios.get(
`${BACKEND_URL}/profiles/${profile.id}/comments`
);
})
.then((response) => {
setComments(response.data);
});
};
// Store a new JSX element for each comment
const commentElements = comments
? comments.map((comment) => (
<ol key={comment.id}>
{comment.createdAt} | {comment.content}
</ol>
))
: [];
return (
<div className="App">
<form onSubmit={handleSubmit}>
<input
// Use textarea to give user more space to type
as="textarea"
name="content"
value={comments}
onChange={handleChange}
/>
<button variant="primary" type="submit">
Submit
</button>
</form>
</div>
);
};
export default Single;
Data is being stored in profile.json without the comments: [{"PROFILE_NUMBER": "A123", "NAME": "X", "AGE" : "21", "HOBBY" : "RUN"} , .....]
I am quite new and not sure how to debug it and push my comments into the database.

Try in this method
router.post('/add', (req, res) => {
let { userid, comment } = req.body;
commentModel.create({
userid: userid,
comment: comment
}).then((data) => {
console.log("data-add", data);
return res.json({
success: true,
message: "Comment added",
data: data
})
}).catch((error) => {
console.log("error: ", error);
return res.json({
success: false,
message: error.message,
data: []
})
})
});

Related

Database updates only on the first click of the button with this functions, what is wrong and how could i fix it?

I am new to react and MongoDB, I am trying to add months to a date in my database in mongo, but it only updates the first time I click on the <Price> button, I need it to update every time I click it. The user has to log out and log back in for it to work again, but still only 1 update can be made to the database. Can someone explain to me why this is happening, and how could it be fixed?
This is the function
import React, { useContext } from "react";
import { useState } from "react";
import useFetch from "../../hooks/useFetch";
import Footer from "../../components/Footer";
import Navbar from "../../components/Navbar";
import Sidebar from "../../components/Sidebar";
import {
ContractContainer,
HeadingContainer,
TypeH1,
ActiveUntil,
MonthlyWrapper,
MonthlyContainer,
MonthNumber,
Price,
Navbarback,
} from "./userinfoElements";
import { AuthContext } from "../../context/AuthContext";
import moment from "moment";
import axios from "axios";
const Userinfo = () => {
// for nav bars
const [isOpen, setIsOpen] = useState(false);
// set state to true if false
const toggle = () => {
setIsOpen(!isOpen);
};
const { user } = useContext(AuthContext);
let { data, loading, reFetch } = useFetch(`/contracts/${user.contractType}`);
let dateFormat = moment(user.activeUntil).format("DD/MMMM/yyyy");
const updateDate = async () => {
try {
let newDate = moment(user.activeUntil).add(1, "months");
dateFormat = newDate.format("DD/MMMM/yyyy");
axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
} catch (err) {
console.log(err);
}
reFetch();
};
return (
<>
<Sidebar isOpen={isOpen} toggle={toggle} />
{/* navbar for smaller screens*/}
<Navbar toggle={toggle} />
<Navbarback /> {/* filling for transparent bacground navbar*/}
{loading ? (
"Loading components, please wait"
) : (
<>
<ContractContainer>
<HeadingContainer>
<TypeH1>{data.contractType}</TypeH1>
<ActiveUntil>Subscription active until {dateFormat}</ActiveUntil>
</HeadingContainer>
<MonthlyWrapper>
<MonthlyContainer>
<MonthNumber>1 Month</MonthNumber>
<Price onClick={updateDate}>{data.month1Price}$</Price>
</MonthlyContainer>
<MonthlyContainer>
<MonthNumber>3 Month</MonthNumber>
<Price onClick={updateDate}>{data.month3Price}$</Price>
</MonthlyContainer>
<MonthlyContainer>
<MonthNumber>6Month</MonthNumber>
<Price onClick={updateDate}>{data.month6Price}$</Price>
</MonthlyContainer>
<MonthlyContainer>
<MonthNumber>12Month</MonthNumber>
<Price onClick={updateDate}>{data.month12Price}$</Price>
</MonthlyContainer>
</MonthlyWrapper>
</ContractContainer>
</>
)}
<Footer />
</>
);
};
export default Userinfo;
this is the fetch hook
import { useEffect, useState } from "react";
import axios from "axios";
const useFetch = (url) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const res = await axios.get(url);
setData(res.data);
} catch (err) {
setError(err);
}
setLoading(false);
};
fetchData();
}, [url]);
const reFetch = async () => {
setLoading(true);
try {
const res = await axios.get(url);
setData(res.data);
} catch (err) {
setError(err);
}
setLoading(false);
};
return { data, loading, error, reFetch };
};
export default useFetch;
Any help is appreciated!
EDIT: added AuthContext file and server sided controllers if needed
import React from "react";
import { createContext, useEffect, useReducer } from "react";
const INITIAL_STATE = {
user: JSON.parse(localStorage.getItem("user")) || null,
loading: false,
error: null,
};
export const AuthContext = createContext(INITIAL_STATE);
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN_START":
return {
user: null,
loading: true,
error: null,
};
case "LOGIN_SUCCESS":
return {
user: action.payload,
loading: false,
error: null,
};
case "LOGIN_FAILURE":
return {
user: null,
loading: false,
error: action.payload,
};
case "LOGOUT":
return {
user: null,
loading: false,
error: null,
};
default:
return state;
}
};
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
useEffect(() => {
localStorage.setItem("user", JSON.stringify(state.user));
}, [state.user]);
return (
<AuthContext.Provider
value={{
user: state.user,
loading: state.loading,
error: state.error,
dispatch,
}}
>
{children}
</AuthContext.Provider>
);
};
api Controller to update active date
import User from "../models/User.js";
export const updateActiveDate = async (req, res, next) => {
try {
await User.updateOne({ $set: { activeUntil: req.body.activeUntil } });
res.status(200).json("Active date has been updated.");
} catch (err) {
next(err);
}
};
api Controller to find contracts
import Contracts from "../models/Contracts.js";
export const getContract = async (req, res, next) => {
try {
const Contract = await Contracts.findOne({
contractType: req.params.contractType,
});
res.status(200).json(Contract);
} catch (err) {
next(err);
}
};
api Controller for login authentication
export const login = async (req, res, next) => {
try {
const user = await User.findOne({ namekey: req.body.namekey });
if (!user) return next(createError(404, "User not found!"));
if (req.body.password === undefined) {
return next(createError(500, "Wrong password or namekey!"));
}
const isPasswordCorrect = await bcrypt.compare(
req.body.password,
user.password
);
if (!isPasswordCorrect)
return next(createError(400, "Wrong password or namekey!"));
const token = jwt.sign({ id: user._id }, process.env.JWT);
const { password, ...otherDetails } = user._doc;
res
.cookie("access_token", token, {
httpOnly: true,
})
.status(200)
.json({ details: { ...otherDetails } });
} catch (err) {
next(err);
}
};
You should update the stored user state to reflect the activeUntil date change.
Define a 'UPDATE_USER_DATE' action in your reducer to update the user instance:
case "UPDATE_USER_DATE":
const updatedUser = { ...state.user };
updatedUser.activeUntil = action.payload;
return {
...state,
user: updatedUser
};
Then, after updating the date in updateDate, update the user state as well:
const { user, dispatch } = useContext(AuthContext);
const updateDate = async () => {
try {
let newDate = moment(user.activeUntil).add(1, "months");
dateFormat = newDate.format("DD/MMMM/yyyy");
await axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
dispatch({ type: "UPDATE_USER_DATE", payload: newDate });
} catch (err) {
console.log(err);
}
reFetch();
};
Give this a try. It awaits the put request, and only once that has responded it calls reFetch. Without the await you're calling the reFetch before the put request has had a chance to complete its work.
const updateDate = async () => {
try {
let newDate = moment(user.activeUntil).add(1, "months");
dateFormat = newDate.format("DD/MMMM/yyyy");
await axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
} catch (err) {
console.log(err);
} finally {
reFetch();
}
};

MERN Application - Problem with notifications and mapping array

I'm on a MERN stack to create a chat application.
And when I'm trying to display notifications when there is a new message in another chatroom or a new PM I'm having this issue :
Sidebar.js:99 Uncaught TypeError: Cannot read properties of undefined (reading 'Tech')
at Sidebar.js:99:1
at Array.map (<anonymous>)
Tech is a room in an array, server side.
And problem is on front side :
<ul className='chatroom'>
{rooms.map((room, idx) => (
<li
key={idx}
onClick={() => joinRoom(room)}
className={room === currentRoom ? 'actif' : ''}
>
{room}
{currentRoom !== room &&
<span className='notifs'>
{user.newMessages[room]}
</span>
}
</li>
))}
</ul>
on this part particulary :
{currentRoom !== room &&
<span className='notifs'>
{user.newMessages[room]}
</span>
I tried a lot of things, starting by testing if the state is here with a ternary or a conditional. But nothing more. And it works because the first part with {room} is working great, all rooms displays on a list.
I'm on it since three days, I'm losing all my hairs 🤣 so if you have any idea...
I'm putting parts of the code if it can help
Sidebar.js
import React, { useContext, useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { AppContext } from '../../context/appContext';
import { Div } from './Sidebar.elements'
import { addNotifications, resetNotifications } from '../../features/userSlice'
import { FaCircle } from 'react-icons/fa'
function Sidebar() {
const user = useSelector((state) => state.user);
const dispatch = useDispatch();
const {
socket,
setMembers,
members,
setCurrentRoom,
setRooms,
privateMemberMsg,
rooms,
setPrivateMemberMsg,
currentRoom
} = useContext(AppContext)
function joinRoom(room, isPublic = true){
if(!user){
return alert("Veuillez vous identifier");
}
socket.emit("join-room", room);
setCurrentRoom(room);
if (isPublic){
setPrivateMemberMsg(null);
}
// dispatch for notifications
dispatch(resetNotifications(room));
}
socket.off("notifications").on("notifications", (room) => {
if(currentRoom !== room) dispatch(addNotifications(room));
console.log(room);
});
function getRooms(){
fetch("http://localhost:5001/rooms")
.then((res) => res.json())
.then((data) => setRooms(data));
}
useEffect(()=>{
if(user){
setCurrentRoom('General');
getRooms();
socket.emit('join-room', 'General');
socket.emit('new-user');
}
},[])
socket.off('new-user').on('new-user', (payload) => {
setMembers(payload);
})
function orderIds(id1, id2) {
if(id1 > id2) {
return id1 + "-" + id2;
} else {
return id2 + "-" + id1;
}
}
function handlePrivateMemberMsg(member){
setPrivateMemberMsg(member);
const roomId = orderIds(user._id, member._id);
joinRoom(roomId, false);
}
if(!user){
return <></>;
}
return (
<>
<Div>
<h2>Chatrooms disponibles</h2>
<ul className='chatroom'>
{rooms.map((room, idx) => (
<li
key={idx}
onClick={() => joinRoom(room)}
className={room === currentRoom ? 'actif' : ''}
>
{room}
{currentRoom !== room &&
<span className='notifs'>
{user.newMessages[room]}
</span>
}
</li>
))}
</ul>
<h2 className='chat'>Chaters</h2>
<ul className='membre'>
{members.map((member) => (
<li
key={member.id}
className={`
${privateMemberMsg?._id === member?._id ? 'actif' : ''}
${member._id === user._id ? 'disabling' : ''}
`}
onClick={() => handlePrivateMemberMsg(member)}
>
<div className="row">
<div className="col-2">
<img src={member.picture} className='member-pic'/>
{member.status == "En ligne" ? <FaCircle className='online'/> : <FaCircle className='offline'/>}
</div>
<div className="col-9">
{member.name}
{member._id == user?._id && " (Vous)"}
{member.status == "Hors Ligne" && <span className="offtxt">(Offline)</span> }
</div>
<div className="col-1">
<span className="notifs">
{/* {user.newMessages[orderIds(member._id, user._id)]} */}
</span>
</div>
</div>
</li>
))}
</ul>
</Div>
</>
)
}
export default Sidebar
appApi.js
import { createApi, fetchBaseQuery } from '#reduxjs/toolkit/query/react'
// define a service user and base URL
const appApi = createApi({
reducerPath: 'appApi',
baseQuery: fetchBaseQuery({
baseUrl: 'http://localhost:5001'
}),
endpoints: (builder) => ({
// creating the user
signupUser: builder.mutation({
query: (user) => ({
url: '/users',
method: "POST",
body: user,
}),
}),
// login
loginUser: builder.mutation({
query: (user) => ({
url:'/users/login',
method: "POST",
body: user,
}),
}),
//logout
logoutUser: builder.mutation({
query: (payload) => ({
url: '/logout',
method: "DELETE",
body: payload,
}),
}),
}),
});
export const { useSignupUserMutation, useLoginUserMutation, useLogoutUserMutation } = appApi;
export default appApi;
appContext.js
import { io } from 'socket.io-client'
import React from 'react'
const SOCKET_URL = "http://localhost:5001";
export const socket = io(SOCKET_URL);
//app context
export const AppContext = React.createContext()
userSlice.js
import { createSlice } from "#reduxjs/toolkit";
import appApi from "../services/appApi";
export const userSlice = createSlice({
name: "user",
initialState: null,
reducers: {
addNotifications: (state, { payload }) => {
if (state.newMessages[payload]) {
state.newMessages[payload] = state.newMessages[payload] + 1;
} else {
state.newMessages[payload] = 1;
}
},
resetNotifications: (state, { payload }) => {
delete state.newMessages[payload];
},
},
extraReducers: (builder) => {
// save user after signup
builder.addMatcher(appApi.endpoints.signupUser.matchFulfilled, (state, { payload }) => payload);
// save user after login
builder.addMatcher(appApi.endpoints.loginUser.matchFulfilled, (state, { payload }) => payload);
// logout : destroy user session
builder.addMatcher(appApi.endpoints.logoutUser.matchFulfilled, () => null);
},
})
export const { addNotifications, resetNotifications } = userSlice.actions;
export default userSlice.reducer;
server.js on back side
const express = require('express');
const app = express();
const userRoutes = require('./routes/userRoutes');
const User = require('./models/User');
const Message = require('./models/Message');
const rooms = [
'General',
'Tech',
'Crypto',
'Gaming'
];
const cors = require('cors');
app.use(express.urlencoded({extended: true}));
app.use(express.json());
app.use(cors());
app.use('/users', userRoutes)
require('./connection')
const server = require('http').createServer(app);
const PORT = 5001;
const io = require('socket.io')(server, {
cors: {
origin: 'http://localhost:3000',
methods: ['GET', 'POST']
}
})
app.get('/rooms', (req, res)=> {
res.json(rooms)
})
async function getLastMessagesFromRoom(room){
let roomMessages = await Message.aggregate([
{$match: {to: room}},
{$group: {_id: '$date', messagesByDate: {$push: '$$ROOT'}}}
])
return roomMessages;
}
function sortRoomMessagesByDate(messages){
return messages.sort(function(a, b){
let date1 = a._id.split('/');
let date2 = b._id.split('/');
date1 = date1[2] + date1[0] + date1[1];
date2 = date2[2] + date2[0] + date2[1];
return date1 < date2 ? -1 : 1
})
}
// socket connection
io.on('connection', (socket)=> {
socket.on('new-user', async ()=>{
const members = await User.find();
io.emit('new-user', members);
})
socket.on('join-room', async(room)=> {
socket.join(room);
let roomMessages = await getLastMessagesFromRoom(room);
roomMessages = sortRoomMessagesByDate(roomMessages);
socket.emit('room-messages', roomMessages);
})
socket.on('message-room', async(room, content, sender, time, date) => {
const newMessage = await Message.create({content, from: sender, time, date, to: room});
let roomMessages = await getLastMessagesFromRoom(room);
roomMessages = sortRoomMessagesByDate(roomMessages);
// sending message to room
io.to(room).emit('room-messages', roomMessages);
// And send notification when sending new message
socket.broadcast.emit('notifications', room)
})
app.delete('/logout', async(req, res)=>{
try {
const {_id, newMessages} = req.body;
const user = await User.findById(_id);
user.status = "Hors Ligne";
user.newMessages = newMessages;
await user.save();
const members = await User.find();
socket.broadcast.emit('new-user', members);
res.status(200).send();
} catch (e) {
console.log(e);
res.status(400).send();
}
})
})
server.listen(PORT, () => {
console.log('listening to port ', PORT);
})
Edit
Adding the App.js that I forgot
App.js
import './App.css';
import {
BrowserRouter as Router,
Route,
Routes
} from 'react-router-dom'
import Home from './pages/Home';
import Login from './pages/Login';
import Signup from './pages/Signup';
import Chat from './pages/Chat';
import GlobalStyle from './GlobalStyles';
import { useSelector } from 'react-redux';
import { useState } from 'react';
import { AppContext, socket } from './context/appContext'
function App() {
const [rooms, setRooms] = useState([]);
const [currentRoom, setCurrentRoom] = useState([]);
const [members, setMembers] = useState([]);
const [messages, setMessages] = useState([]);
const [privateMemberMsg, setPrivateMemberMsg] = useState({});
const [newMessages, setNewMessages] = useState({});
const user = useSelector((state) => state.user);
return (
<AppContext.Provider value={{
socket,
currentRoom,
setCurrentRoom,
members,
setMembers,
messages,
setMessages,
privateMemberMsg,
setPrivateMemberMsg,
newMessages,
setNewMessages,
rooms,
setRooms
}} >
<Router>
<GlobalStyle/>
<Routes>
<Route path="/" element={<Home/>} />
{!user && (
<>
<Route path="/login" element={<Login/>} />
<Route path="/signup" element={<Signup/>} />
</>
)}
<Route path="/chat" element={<Chat/>} />
</Routes>
</Router>
</AppContext.Provider>
);
}
export default App;

Trying to submit a user score with a button in react. Getting error message of saveScore is not a function

I am building a react application and part of the application is a quiz section. At the end of the quiz there is a button which can save the user score in the quiz to the database.
This is my express route
// #route Put api/profile/saveScore/:id
// #desc Save users quiz score to profile
// #access Private
router.put('/saveScore/:topic_id', checkObjectId('topic_id'), auth, async (req, {params: {topic_id } }, res) => {
const score = req.body.score
const topic = topic_id
const newUserTopic = {
score,
topic,
}
try {
const profile = await Profile.findOne({ user: req.user.id });
profile.topics.unshift(newUserTopic);
await profile.save();
res.json(profile)
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
})
The express route works no bother in postman so thinking the issue must be more on the react side.
This is my action route
// Save Quiz Score to users profile
export const saveScore = (topicId, payload) => async (dispatch) => {
try {
const res = await api.put(`/profile/saveScore/${topicId}`, payload);
dispatch({
type: GET_PROFILE,
payload: res.data
});
dispatch(setAlert('Topic Saved', 'success'));
} catch (err) {
const errors = err.response.data.errors;
if(errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')))
}
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
}
};
This is my component
import React, { useEffect, useState, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import QuizItem from './QuizItem';
import { getTopicById } from '../../actions/topic';
import { saveScore} from '../../actions/profile';
import { SaveScoreForm } from './SaveScoreForm';
const Quiz = ({ getTopicById, saveScore, topic: { topic, loading }, match }) => {
useEffect(() => {
getTopicById(match.params.id);
}, [getTopicById, match.params.id])
const [currentIndex, setCurrentIndex] = useState(0);
const [score, setScore] = useState(0);
const [showAnswers, setShowAnswers] = useState(false)
const [formData, setFormData] = useState({ score })
const handleAnswer = (answer) => {
if(!showAnswers) {
if(answer === topic[currentIndex].correct_answer) {
setScore(score + 1);
}
}
setShowAnswers(true);
};
const handleNextQuestion = () => {
setShowAnswers(false);
setCurrentIndex(currentIndex + 1);
}
console.log(currentIndex)
const onChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value })
}
const onSubmit = (e) => {
e.preventDefault();
const payload = new FormData();
payload.append('score', formData.score)
saveScore(payload, match.params.id);
}
return topic.length > 0 ? (
<div className='container'>
{currentIndex >= topic.length ? (
<Fragment>
<SaveScoreForm topic={topic} score={score} />
<form
onSubmit={e => onSubmit(e)}
>
<input
type='hidden'
value={score}
onChange={(e) => onChange(e)}
/>
<input type='submit' className='btn btn-primary1 my-1' />
</form>
</Fragment>
) : (
<QuizItem
key={topic.question}
topic={topic[currentIndex]}
showAnswers={showAnswers}
handleNextQuestion={handleNextQuestion}
handleAnswer={handleAnswer}
/>
)}
</div>
) : (
<Spinner/>
)
}
Quiz.prototype = {
getTopicById: PropTypes.func.isRequired,
topic: PropTypes.object.isRequired
}
const mapStateToProps = state => ({
topic: state.topic,
showAnswers: state.showAnswers,
handleNextQuestion: state.handleNextQuestion,
handleAnswer: state.handleAnswer
})
export default connect(mapStateToProps, { getTopicById })(Quiz)
Child component
import React from 'react'
export const SaveScoreForm = ({ score, topic, }) => {
return (
<div>
<div className='bg-primary1 p-2 my-4'>
<h1 className='large'>Review Your Score</h1>
<p className="lead">Quiz ended! Your score is: {(score/topic.length) * 100}%</p>
<p>Save your score to your profile or take the quiz again!</p>
</div>
</div>
);
};
export default SaveScoreForm;
TypeError: saveScore is not a function
Any help or pointers in the right direction would be very much appreciated.
Thanks
You are importing import { saveScore} from '../../actions/profile';
But then you have this prop
const Quiz = ({ getTopicById, saveScore
// ----------------------------^
which is overriding saveScore in your components context. Unless you are passing a saveScore prop while initialising <Quiz> it'll be undefined.
If you want to import the saveScore module just remove this prop variable.

When I first click the delete button, all the notes are disappeared but, when I refresh the page it works just fine

I tried almost everything. I tested my API to ensure that the problem is not with it. It happens just one time when you visit the page. However, when you refresh it works fine.
Before Clicking delete:
Click here to see the image
After clicking delete button: Click here to see the image
Backend Route (home.route.js):
const express = require("express");
const mongoose = require("mongoose");
const Note = require("../models/note.model.js");
const router = express.Router();
router.get("/", (req, res) => {
Note.find()
.then(notes => res.json(notes))
.catch(err => res.status(400).json("Error: " + err));
});
router.post("/", (req, res) => {
const note = req.body;
const newNote = new Note(note);
newNote.save()
.then(() => res.json("Note added"))
.catch(err => res.status(400).json("Error: " + err));
});
router.delete("/", (req, res) => {
const idWeGotFromReact = req.body.noteId;
Note.findByIdAndDelete(idWeGotFromReact)
.then(() => res.json('Note deleted.'))
.catch(err => res.status(400).json('Error: ' + err));
})
module.exports = router;
React Code:
Code for the Note Component:
import React from "react";
import axios from 'axios';
import DeleteIcon from '#material-ui/icons/Delete';
function Note(props) {
function handleClick() {
const id = props.noteId;
axios({
method: 'delete',
url: 'http://localhost:5000',
data: {
noteId: id
}
});
props.onDelete(id);
}
return (
<div className="note">
<h1>{props.title}</h1>
<p>{props.content}</p>
<button name={props.noteId} onClick={handleClick}>
<DeleteIcon />
</button>
</div>
);
}
export default Note;
Code for the Main App component:
import React, { useState, useEffect } from "react";
import axios from "axios";
import Header from "./Header";
import Footer from "./Footer";
import Note from "./Note";
import CreateArea from "./CreateArea";
function App() {
const [notes, setNotes] = useState([]);
useEffect(() => {
axios
.get('http://localhost:5000')
.then(res => {
setNotes(res.data);
});
}, []);
function addNote(newNote) {
setNotes(prevNotes => {
return [...prevNotes, newNote];
});
}
function deleteNote(id) {
setNotes(notes => {
return notes.filter((noteItem) => {
return noteItem._id !== id;
});
});
}
function NoteList() {
return (
notes.map((noteItem) => {
return (
<Note
key={noteItem._id}
id={noteItem._id}
title={noteItem.title}
content={noteItem.content}
onDelete={deleteNote}
noteId={noteItem._id}
/>
);
})
);
}
return (
<div>
<Header />
<CreateArea onAdd={addNote} />
<NoteList />
<Footer />
</div>
);
}
export default App;
Input Component:
import React, { useState } from "react";
import axios from "axios";
function CreateArea(props) {
const [note, setNote] = useState({
title: "",
content: ""
});
function handleChange(event) {
const { name, value } = event.target;
setNote(prevNote => {
return {
...prevNote,
[name]: value
};
});
}
function submitNote(event) {
props.onAdd(note);
setNote({
title: "",
content: ""
});
event.preventDefault();
axios
.post('http://localhost:5000', note)
.then(res => {
console.log(res);
console.log(res.data);
});
}
return (
<div>
<form action="/" method="post">
<input
name="title"
onChange={handleChange}
value={note.title}
placeholder="Title"
/>
<textarea
name="content"
onChange={handleChange}
value={note.content}
placeholder="Take a note..."
rows="3"
/>
<button className="button" onClick={submitNote}>Add</button>
</form>
</div>
);
}
export default CreateArea;
I would also appreciate if you can provide a review of my code because I am a beginner and that would definitely help me.
Try this approach, (just an assumption)
function deleteNote(id) {
setNotes(notes => {
const filteredNotes = notes.filter((noteItem) => {
return noteItem._id !== id;
});
return [...filteredNotes];
});
}

How to prevent React state update for asynchronous request on unmounted component? [duplicate]

This question already has answers here:
How to cancel a fetch on componentWillUnmount
(16 answers)
Closed 2 years ago.
I'm working on a mernstack app where I have a custom hook for API requests with useReducer's state and dispatch functions that is loaded into the context api. Usually GET request runs smoothly on page load, but every time I use the POST, PATCH, PUT, and DELETE request functions it causes a component to unmount and get this error:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
The error goes away whenever I refresh the page and see the changes. How to prevent React state update for asynchronous request on unmounted component?
Database Setup
const mongodb = require('mongodb');
const { MongoClient, ObjectID } = mongodb;
require('dotenv').config();
const mongourl = process.env.MONGO_URI;
const db_name = process.env.DB_NAME;
let db;
async function startConnection(cb) {
let client;
try {
client = await MongoClient.connect(mongourl, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
db = client.db(db_name);
await cb();
} catch (err) {
await cb(err);
}
}
const getDb = () => {
return db;
};
const getPrimaryKey = (_id) => {
return ObjectID(_id);
};
module.exports = { db, startConnection, getDb, getPrimaryKey };
Server:
const express = require('express');
require('dotenv').config();
const port = process.env.PORT || 8000;
const db = require('./db');
const db_col = process.env.DB_COL;
const router = express.Router();
let status;
db.startConnection((err) => {
if (err) {
status = `Unable to connect to the database ${err}`;
console.log(status);
} else {
status = 'Connected to the database';
console.log(status);
}
});
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use('/list', router);
router.get('/', (req, res) => {
db.getDb()
.collection(db_col)
.find({})
.toArray((err, docs) => {
if (err) {
console.log(err);
}
res.json(docs);
console.log(docs);
});
});
router.post('/', (req, res) => {
const newlist = req.body;
const { list_name, list_items } = newlist;
db.getDb()
.collection(db_col)
.insertOne({ list_name, list_items }, (err, docs) => {
if (err) {
console.log(err);
}
res.redirect('/');
console.log(docs);
});
});
router.patch('/:id', (req, res) => {
const paramID = req.params.id;
const listname = req.body.list_name;
db.getDb()
.collection(db_col)
.updateOne(
{ _id: db.getPrimaryKey(paramID) },
{ $set: { list_name: listname } },
(err, docs) => {
if (err) {
console.log(err);
}
res.redirect('/');
console.log(docs);
}
);
});
router.put('/:id', (req, res) => {
const paramID = req.params.id;
const listitems = req.body.list_items;
db.getDb()
.collection(db_col)
.updateOne(
{ _id: db.getPrimaryKey(paramID) },
{ $set: { list_items: listitems } },
(err, docs) => {
if (err) {
console.log(err);
}
res.redirect('/');
console.log(docs);
}
);
});
router.delete('/:id', (req, res) => {
const paramID = req.params.id;
db.getDb()
.collection(db_col)
.deleteOne({ _id: db.getPrimaryKey(paramID) }, (err, docs) => {
if (err) {
console.log(err);
}
res.redirect('/');
console.log(docs);
});
});
app.listen(port, console.log(`Server listening to port: ${port}`));
Actions:
import { LOADING, PROCESSING_REQUEST, HANDLING_ERROR } from './actionTypes';
const loading = () => {
return {
type: LOADING,
};
};
const processingRequest = (params) => {
return {
type: PROCESSING_REQUEST,
response: params,
};
};
const handlingError = () => {
return {
type: HANDLING_ERROR,
};
};
export { loading, processingRequest, handlingError };
Reducer:
import {
LOADING,
PROCESSING_REQUEST,
HANDLING_ERROR,
} from './actions/actionTypes';
export const initialState = {
isError: false,
isLoading: false,
data: [],
};
const listReducer = (state, { type, response }) => {
switch (type) {
case LOADING:
return {
...state,
isLoading: true,
isError: false
};
case PROCESSING_REQUEST:
return {
...state,
isLoading: false,
isError: false,
data: response,
};
case HANDLING_ERROR:
return {
...state,
isLoading: false,
isError: true
};
default:
throw new Error();
}
};
export default listReducer;
Custom Hook for API Requests:
import { useEffect, useCallback, useReducer } from 'react';
import axios from 'axios';
import listReducer, { initialState } from '../../context/reducers/reducers';
import {
loading,
processingRequest,
handlingError,
} from '../../context/reducers/actions/actionCreators';
const useApiReq = () => {
const [state, dispatch] = useReducer(listReducer, initialState);
const getRequest = useCallback(async () => {
dispatch(loading());
try {
const response = await axios.get('/list');
dispatch(processingRequest(response.data));
} catch (err) {
dispatch(handlingError);
}
}, []);
const postRequest = useCallback(async (entry) => {
dispatch(loading());
try {
const response = await axios.post('/list', entry);
dispatch(processingRequest(response.data));
} catch (err) {
dispatch(handlingError);
}
}, []);
const patchRequest = useCallback(async (id, updated_entry) => {
dispatch(loading());
try {
const response = await axios.patch(`/list/${id}`, updated_entry);
dispatch(processingRequest(response.data));
} catch (err) {
dispatch(handlingError);
}
}, []);
const putRequest = useCallback(async (id, updated_entry) => {
dispatch(loading());
try {
const response = await axios.put(`/list/${id}`, updated_entry);
dispatch(processingRequest(response.data));
} catch (err) {
dispatch(handlingError);
}
}, []);
const deleteRequest = useCallback(async (id) => {
dispatch(loading());
try {
const response = await axios.delete(`/list/${id}`);
dispatch(processingRequest(response.data));
} catch (err) {
dispatch(handlingError);
}
}, []);
return [
state,
getRequest,
postRequest,
patchRequest,
putRequest,
deleteRequest,
];
};
export default useApiReq;
Context API
import React, { createContext } from 'react';
import useApiReq from '../components/custom-hooks/useApiReq';
export const AppContext = createContext();
const AppContextProvider = (props) => {
const [
state,
getRequest,
postRequest,
patchRequest,
putRequest,
deleteRequest,
] = useApiReq();
return (
<AppContext.Provider
value={{
state,
getRequest,
postRequest,
patchRequest,
putRequest,
deleteRequest,
}}
>
{props.children}
</AppContext.Provider>
);
};
export default AppContextProvider;
App:
import React from 'react';
import AppContextProvider from './context/AppContext';
import Header from './components/header/Header';
import Main from './components/main/Main';
import './stylesheets/styles.scss';
function App() {
return (
<AppContextProvider>
<div className='App'>
<Header />
<Main />
</div>
</AppContextProvider>
);
}
export default App;
Main:
This is where the GET request happens on initial load.
import React, { useEffect, useContext } from 'react';
import { AppContext } from '../../context/AppContext';
import Sidebar from '../sidebar/Sidebar';
import ParentListItem from '../list-templates/ParentListItem';
function Main() {
const { state, getRequest } = useContext(AppContext);
const { isError, isLoading, data } = state;
useEffect(() => {
getRequest();
}, [getRequest]);
return (
<main className='App-body'>
<Sidebar />
<div className='list-area'>
{isLoading && (
<p className='empty-notif'>Loading data from the database</p>
)}
{isError && <p className='empty-notif'>Something went wrong</p>}
{data.length == 0 && <p className='empty-notif'>Database is empty</p>}
<ul className='parent-list'>
{data.map((list) => (
<ParentListItem key={list._id} {...list} />
))}
</ul>
</div>
</main>
);
}
export default Main;
Sidebar
import React, { useState } from 'react';
import Modal from 'react-modal';
import AddList from '../modals/AddList';
import DeleteList from '../modals/DeleteList';
/* Modal */
Modal.setAppElement('#root');
function Sidebar() {
const [addModalStatus, setAddModalStatus] = useState(false);
const [deleteModalStatus, setDeleteModalStatus] = useState(false);
const handleAddModal = () => {
setAddModalStatus((prevState) => !prevState);
};
const handleDeleteModal = () => {
setDeleteModalStatus((prevState) => !prevState);
};
return (
<aside className='sidebar'>
<nav className='nav'>
<button className='btn-rec' onClick={handleAddModal}>
Add
</button>
<button className='btn-rec' onClick={handleDeleteModal}>
Delete
</button>
</nav>
<Modal isOpen={addModalStatus} onRequestClose={handleAddModal}>
<header className='modal-header'>Create New List</header>
<div className='modal-body'>
<AddList exitHandler={handleAddModal} />
</div>
<footer className='modal-footer'>
<button onClick={handleAddModal} className='btn-circle'>
×
</button>
</footer>
</Modal>
<Modal isOpen={deleteModalStatus} onRequestClose={handleDeleteModal}>
<header className='modal-header'>Delete List</header>
<div className='modal-body'>
<DeleteList exitHandler={handleDeleteModal} />
</div>
<footer className='modal-footer'>
<button onClick={handleDeleteModal} className='btn-circle'>
×
</button>
</footer>
</Modal>
</aside>
);
}
export default Sidebar;
Add Modal
This is where the post request is called
import React, { useContext, useEffect, useState, useRef } from 'react';
import { AppContext } from '../../context/AppContext';
const AddList = ({ exitHandler }) => {
const { postRequest } = useContext(AppContext);
const [newList, setNewList] = useState({});
const inputRef = useRef(null);
/* On load set focus on the input */
useEffect(() => {
inputRef.current.focus();
}, []);
const handleAddList = (e) => {
e.preventDefault();
const new_list = {
list_name: inputRef.current.value,
list_items: [],
};
setNewList(new_list);
};
const handleSubmit = (e) => {
e.preventDefault();
postRequest(newList);
exitHandler();
};
return (
<form onSubmit={handleSubmit} className='generic-form'>
<input
type='text'
ref={inputRef}
placeholder='List Name'
onChange={handleAddList}
/>
<input type='submit' value='ADD' className='btn-rec' />
</form>
);
};
export default AddList;
Delete Modal
This is where the Delete Request is called.
import React, { useContext, useEffect, useState, useRef } from 'react';
import { AppContext } from '../../context/AppContext';
const DeleteList = ({ exitHandler }) => {
const { state, deleteRequest } = useContext(AppContext);
const { data } = state;
const selectRef = useRef();
const [targetListId, setTargetListId] = useState();
useEffect(() => {
selectRef.current.focus();
}, []);
useEffect(() => {
setTargetListId(data[0]._id);
}, [data]);
const handleDeleteList = (e) => {
e.preventDefault();
deleteRequest(targetListId);
exitHandler();
};
const handleChangeList = (e) => {
setTargetListId(e.target.value);
};
return (
<form onSubmit={handleDeleteList} className='generic-form'>
<label>
<select
ref={selectRef}
value={targetListId}
onChange={handleChangeList}
className='custom-select'
>
{data.map((list) => (
<option key={list._id} value={list._id}>
{list.list_name}
</option>
))}
</select>
</label>
<input type='submit' value='DELETE' className='btn-rec' />
</form>
);
};
export default DeleteList;
Parent List:
This is where the PUT, PATCH request is called
import React, { useContext, useState, useEffect, useRef } from 'react';
import { FaPen, FaCheck } from 'react-icons/fa';
import ChildListItem from './ChildListItem';
import { AppContext } from '../../context/AppContext';
import displayDate from '../../utilities/utilities';
import { v4 } from 'uuid';
function ParentListItem({ _id, list_name, list_items }) {
const { patchRequest, putRequest } = useContext(AppContext);
const [activeListItems, setActiveListItems] = useState([]);
const [completedListItems, setCompletedListItems] = useState([]);
const [listItems, setListItems] = useState({});
const [disabledInput, setDisabledInput] = useState(true);
const [title, setTitle] = useState({});
const [status, setStatus] = useState(false);
const titleRef = useRef();
const { day, date, month, year, current_time } = displayDate();
const handleCreateNewItem = (e) => {
const newItem = {
item_id: v4(),
item_name: e.target.value,
item_date_created: `${day}, ${date} of ${month} ${year} at ${current_time}`,
isComplete: false,
};
const new_list_items = [...list_items, newItem];
setListItems({ list_items: new_list_items });
};
/* Handles the edit list title button */
const toggleEdit = () => {
setDisabledInput(!disabledInput);
};
/* Handles the edit list title button */
const toggleStatus = (item_id) => {
const target = list_items.find((item) => item.item_id == item_id);
let updated_list = [...list_items];
updated_list.map((list) => {
if (list == target) {
list.isComplete = !list.isComplete;
}
});
const update = { list_items: updated_list };
putRequest(_id, update);
};
/* Handles the edit list title button */
const deleteItem = (item_id) => {
const target = list_items.find((item) => item.item_id == item_id);
let updated_list = [...list_items].filter((list) => {
if (target.isComplete == true) {
return list !== target;
}
});
const update = { list_items: updated_list };
putRequest(_id, update);
};
/* Handles the edit list tile input */
const handleTitleChange = (e) => {
const newTitle = { list_name: e.target.value };
setTitle(newTitle);
};
/* Handles the submit or dispatched of edited list tile*/
const handleUpdateTitle = (e) => {
e.preventDefault();
patchRequest(_id, title);
setDisabledInput(!disabledInput);
};
const handleSubmitItem = (e) => {
e.preventDefault();
putRequest(_id, listItems);
[e.target.name] = '';
};
useEffect(
(e) => {
if (disabledInput === false) titleRef.current.focus();
},
[disabledInput]
);
useEffect(() => {
setTitle(list_name);
}, [list_name]);
useEffect(() => {
/* On load filter the active list */
let active_list_items = list_items.filter(
(item) => item.isComplete === false
);
setActiveListItems(active_list_items);
}, [list_items]);
useEffect(() => {
/* On load filter the completed list */
let completed_list_items = list_items.filter(
(item) => item.isComplete === true
);
setCompletedListItems(completed_list_items);
}, [list_items]);
return (
<li className='parent-list-item'>
<header className='p-li-header'>
<input
type='text'
className='edit-input'
name='newlist'
ref={titleRef}
defaultValue={list_name}
onChange={handleTitleChange}
disabled={disabledInput}
/>
{disabledInput === true ? (
<button className='btn-icon' onClick={toggleEdit}>
<FaPen />
</button>
) : (
<form onSubmit={handleUpdateTitle}>
<button className='btn-icon' type='submit'>
<FaCheck />
</button>
</form>
)}
</header>
<div id={_id} className='p-li-form-container'>
<form className='generic-form clouds' onSubmit={handleSubmitItem}>
<input
type='text'
placeholder='Add Item'
name='itemname'
onChange={handleCreateNewItem}
/>
<input type='submit' value='+' className='btn-circle' />
</form>
</div>
<div
className={list_items.length === 0 ? 'p-li-area hidden' : 'p-li-area'}
>
<section className='pi-child-list-container'>
<h6>Active: {activeListItems.length}</h6>
{activeListItems.length === 0 ? (
<p className='empty-notif'>List is empty</p>
) : (
<ul className='child-list'>
{activeListItems.map((list) => (
<ChildListItem
key={list.item_id}
{...list}
list_id={_id}
toggleStatus={toggleStatus}
deleteItem={deleteItem}
/>
))}
</ul>
)}
</section>
<section className='pi-child-list-container'>
<h6>Completed: {completedListItems.length}</h6>
{completedListItems.length === 0 ? (
<p className='empty-notif'>List is empty</p>
) : (
<ul className='child-list'>
{completedListItems.map((list) => (
<ChildListItem
key={list.item_id}
{...list}
list_id={_id}
toggleStatus={toggleStatus}
deleteItem={deleteItem}
/>
))}
</ul>
)}
</section>
</div>
</li>
);
}
export default ParentListItem;
Child List
import React from 'react';
import { IconContext } from 'react-icons';
import { FaTrashAlt, FaRegCircle, FaRegCheckCircle } from 'react-icons/fa';
function ChildListItem({
item_name,
item_id,
item_date_created,
isComplete,
toggleStatus,
deleteItem,
}) {
const handleIsComplete = (e) => {
e.preventDefault();
toggleStatus(item_id);
};
const handleDeleteItem = (e) => {
e.preventDefault();
deleteItem(item_id);
};
return (
<li className='c-li-item' key={item_id}>
<div className='c-li-details'>
<p className='item-name'>{item_name}</p>
<p className='date-details'>Date created: {item_date_created}</p>
</div>
<div className='c-li-cta'>
<label htmlFor={item_id} className='custom-checkbox-label'>
<input
type='checkbox'
id={item_id}
checked={isComplete}
onChange={handleIsComplete}
/>
<span className='btn-icon'>
<IconContext.Provider
value={{ className: 'react-icon ri-success' }}
>
{isComplete === false ? <FaRegCircle /> : <FaRegCheckCircle />}
</IconContext.Provider>
</span>
</label>
<button
className='btn-icon btn-delete'
disabled={!isComplete}
onClick={handleDeleteItem}
>
<IconContext.Provider
value={{
className:
isComplete === false
? 'react-icon ri-disabled'
: 'react-icon ri-danger',
}}
>
<FaTrashAlt />
</IconContext.Provider>
</button>
</div>
</li>
);
}
export default ChildListItem;
The warning occurred because your component received the response but it was already unmounted(stoped rendering)
To fix this you have to cancel the request after the component is unmounted like this using useEffect()'s cleanup function (by return cancel function) and axios like the example below
useEffect( () => {
const CancelToken = axios.CancelToken;
let cancel;
const callAPI = async () => {
try {
let res = await axios.post(`.....`, { cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
cancel = c;
}) });
}
catch (err) {
console.log(err)
}
}
callAPI();
return (cancel);
}, []);
you can read more in axios docs: https://github.com/axios/axios
However, keep in mind this only solves the warning and not the reason it redirects after your post request.

Categories