How to update state in Context using Hooks in MERN Stack Application - node.js

I'm trying to find a way to update my "user" state, but I'm stuck here for 3 days already, I need some help.
Here is my user context:
import React, {useEffect, useState} from 'react';
export const UserContext = React.createContext({})
const UserProvider = UserContext.Provider;
const UserContextProvider = (props) => {
const [user, setUser] = useState({})
useEffect(() => {
fetch(`${API}/auth/user`, {
method: 'GET',
withCredentials: true,
credentials: 'include'
})
.then (response => response.json())
.then (response => {
setUser(response.user)
})
.catch (error => {
console.error (error);
});
}, [setUser])
console.log(user)
return (
<UserProvider value={{user, setUser}}>
{props.children}
</UserProvider>
)
}
export default UserContextProvider;
Here is where I'm trying to update the user. In my case I'm trying to push an object in user.cart array, cuz everything on the back-end is fine, but in the front-end the state is not updating:
First I'm using the UserContext:
const Products = () => {
const {user, setUser} = useContext(UserContext) ...
And then here I'm trying to update the user state, BUT when I click the button it logged me out:
<button className="addTo--Cart--Button--Container">
<FaShoppingCart onClick={() => {addToCart(user._id, product); setUser(oldState => oldState.cart.push(product))}}/>
</button>
After this logged me out, the console.log(user) which is in UserContextProvider function log only the user.cart updated lenght.
AND one more:
How to remove item from context:
Here is my remove function:
const removeFromContextCart = (id) => {
console.log(id)
const updatedCart = user.cart.filter((item) => item.id !== id);
setUser(oldState => ({
...oldState,
cart: [
...oldState.cart,
updatedCart
]
}))
}
And my button:
<button className="remove--Button" onClick={() => {removeFromCart(user._id, product); setUser(removeFromContextCart(product._id))}}> REMOVE</button>

Try updating the user state in this way
setUser(oldState => ({
...oldState,
cart: [
...oldState.cart,
product
]
}))

Related

Delete request not working in React+Express+MongoDB app

im working through fullstackopen course along TOP, every excercise went well so I drifted off the course to build simple todo app to solidify the knowledge i gained so far. So i developed front end with react, then the back end with node express connected to mongoDB. All seemed fine but then the delete request stopped working - every other request works fine, only the delete causes errors. After requesting a delete the page crashes, BUT in the database the request is fulfilled and the note is removed. So when I reconnect to the node server and refresh the page, the content is up to date and everything seems to work again.
RESTclient is saying that delete request works fine. But in the browser, when i click delete button, after like a second the app crashes and this is shown in the console:
Notes.js:20 Uncaught TypeError: Cannot read properties of null (reading 'id')
at Notes.js:20:27
at Array.map (<anonymous>)
at b (Notes.js:19:16)
at xo (react-dom.production.min.js:167:137)
at Pi (react-dom.production.min.js:197:258)
at Eu (react-dom.production.min.js:292:88)
at bs (react-dom.production.min.js:280:389)
at gs (react-dom.production.min.js:280:320)
at vs (react-dom.production.min.js:280:180)
at ls (react-dom.production.min.js:271:88)
server.js:
require("dotenv").config();
const express = require("express");
const morgan = require("morgan");
const cors = require("cors");
const mongoose = require("mongoose");
const Note = require("./models/note");
const app = express();
app.use(express.static("build"));
app.use(express.json());
app.set("json spaces", 2);
app.use(cors());
app.use(morgan("tiny"));
/// DEFINE DEFAULT PORT //
const PORT = process.env.PORT;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
/// DEFINE BASIC ROUTES
app.get("/notes", (request, response, next) => {
Note.find({}).then((notes) => response.json(notes));
});
app.get("/notes/:id", (request, response, next) => {
Note.findById(request.params.id)
.then((note) => {
if (note) {
response.json(note);
} else {
response.status(404).end();
}
})
.catch((error) => next(error));
});
/// DELETE ///
app.delete("/notes/:id", (request, response, next) => {
Note.findByIdAndRemove(request.params.id)
.then((response) => response.status(204).end())
.catch((error) => next(error));
});
/// UPDATE ///
app.put("/notes/:id", (request, response, next) => {
const {content, done} = request.body
Note.findByIdAndUpdate(
request.params.id,
{content, done},
{new: true, runValidators: true, context: "query"},
)
.then(updatedNote => response.json(updatedNote))
.catch(error => next(error))
})
/// ADD ///
app.post("/notes", (request, response, next) => {
const body = request.body;
if (!body.content) {
return response.status(400).json({
error: "content missing",
});
}
const note = new Note({
content: body.content,
done: false,
});
note
.save()
.then((saved) => response.json(saved))
.catch((error) => next(error));
});
/// HANDLE UNDEFINED ROUTES ///
const unknownEndpoint = (request, response) => {
response.status(404).send({ error: "unknown endpoint" });
};
app.use(unknownEndpoint);
/// HANDLE ERRORS ///
const errorHandler = (error, request, response, next) => {
console.error(error.message);
if (error.name === "CastError") {
return response.status(400).send({ error: "malformatted id" });
} else if (error.name === "ValidationError") {
return response.status(400).json({ error: error.message });
}
next(error);
};
app.use(errorHandler);
front-end,
app.js:
import { useState, useEffect } from "react";
import css from "./App.css"
import Button from "./Button";
import Input from "./Input";
import noteService from "./services/NoteService";
import Notes from "./Notes";
function App() {
const [notes, setNotes] = useState([]);
const [newNote, setNewNote] = useState("");
useEffect(() => {
noteService.getAll().then((response) => {
setNotes(response);
});
}, []);
const handleInput = (event) => {
const content = event.target.value;
setNewNote(content);
};
const handleSubmit = (event) => {
event.preventDefault();
const note = { content: newNote, done: false };
noteService
.create(note)
.then((response) => setNotes(notes.concat(response)));
setNewNote("");
};
const handleDelete = (id) => {
noteService
.trash(id)
.then(setNotes(notes.filter((note) => note.id !== id)));
};
const toggleStatus = (id) => {
const note = notes.find((item) => item.id === id);
const updated = { ...note, done: !note.done };
noteService.update(id, updated).then((response) => {
setNotes(notes.map((note) => (note.id !== id ? note : response)));
});
};
const showDone = () => {
noteService.getAll().then((response) => {
setNotes(response.filter((note) => note.done));
});
};
const showUndone = () => {
noteService.getAll().then((response) => {
setNotes(response.filter((note) => !note.done));
});
};
const showAll = () => {
noteService.getAll().then((response) => {
setNotes(response);
});
};
return (
<div className="container">
<h1>TO_DO NOTES</h1>
<div className="header">
<Input action={handleInput} value={newNote} />
<Button text={"Add"} action={handleSubmit} />
</div>
<div>
<Button text="Show All" action={showAll} />
<Button text="Show Done" action={showDone} />
<Button text="Show Undone" action={showUndone} />
</div>
<Notes notes={notes} action={handleDelete} toggle={toggleStatus}/>
</div>
);
}
export default App;
Notes.js:
import Button from "./Button";
import css from "./Notes.css";
const Note = ({ item, action, toggle }) => {
return (
<li
onClick={() => toggle(item.id)}
className={item.done ? "done" : "undone"}
>
{item.content} <Button text="x" action={() => action(item.id)} />
</li>
);
};
const Notes = ({ notes, action, toggle }) => {
return (
<>
<ul>
{notes.map((item) => (
<Note key={item.id} item={item} action={action} toggle={toggle} />
))}
</ul>
</>
);
};
export default Notes;
NoteService.js:
import axios from "axios";
const baseUrl = "/notes";
const getAll = () => {
const request = axios.get(baseUrl);
return request.then((response) => response.data);
};
const create = (newObject) => {
const request = axios.post(baseUrl, newObject);
return request.then((response) => response.data);
};
const update = (id, newObject) => {
const request = axios.put(`${baseUrl}/${id}`, newObject);
return request.then((response) => response.data);
};
const trash = id => {
const request = axios.delete(`${baseUrl}/${id}`)
return request.then(result => result.data)
}
export default {
getAll,
create,
update,
trash,
};
I would really appreciate some help. I compared this project with the other one i have thats structured the same, the other one is working but here cannot figure out what is wrong.
In the Notes.js file within the Notes component, where you are iterating using notes.map, change it to notes?.map and see if that works.

Trying to add an array of objects using react useState but getting the error 'Objects are not valid as a React child (found: object with keys {})'

I'm trying to add an array of objects using React useState but I keep getting the error
Objects are not valid as a React child (found: object with keys {})
This is my code in app.js
Note: response.data is getting logged in the console, it's type is an object
const [users, setUsers] = useState([])
useEffect(() => {
Axios.get("http://localhost:3001/").then((response) => {
setUsers([...users, response.data])
}).catch((err) => console.log(err))
}, [])
This is the get api
app.get("/", (req, res) => {
userModel.find({}, (err, result) => {
if(!err) {
res.json(result) //sends 'result' to front end
} else {
res.json(err)
}})
})
The data (result) is being sent to the front end
This is the data I want to add
[{
name: 'John',
age: 20
},
name: 'Doe',
age: 23
}]
Edit: Simple arrays like [0,1,2] are getting added to users, arrays that contain objects arent being added
Edit 2:
This is my whole function:
export default function App() {
const [users, setUsers] = useState([])
useEffect(() => {
Axios.get("http://localhost:3001/").then((response) => {
setUsers([...users, response.data])
}).catch((err) => console.log(err))
}, [])
return(
<>
{users.map((user) => {
return(
<div>
<h1>{user}</h1>
</div>
)
})}
</>
)
}
You have to append the response.data as follows as #Thinker mentioned in the comment section.
useEffect(() => {
Axios.get("http://localhost:3001/")
.then((response) => {
setUsers([...users, ...response.data]);
})
.catch((err) => console.log(err));
}, []);
And then you have to insert it in JSX properly. Currently you're directly giving an object inside <h1>{user}</h1> (It's a violation of JSX syntax). So correct it as follows to include user data correctly. (You can modify it as your preference)
<div>
<h1>{user.name}</h1>
<h1>{user.age}</h1>
</div>

LocalStorage doesn't stay updated on page refresh in MERN Application

LocalStorage somehow doesn't want to stay updated on refresh page. I'm not sure why is that happening, and where is my mistake. The backend is updating fine but the localStorage doesn't stay updated.
Here is where I'm updating localStorage:
const UserContextProvider = (props) => {
const [user, setUser] = useState({})
const [cart, setCart] = useState(localStorage.getItem("cart") || [])
useEffect(() => {
fetch(`${API}/auth/user`, {
method: 'GET',
withCredentials: true,
credentials: 'include'
})
.then (response => response.json())
.then (response => {
setUser(response.user)
})
.catch (error => {
console.error (error);
});
}, [setUser])
useEffect(() => {
localStorage.setItem("cart", JSON.stringify(user.cart))
}, [setUser])
const addToCart = (user, product) => {
fetch(`${API}/cart/usercart`, {
method:"POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify([user._id, product])
})
.then(() => setUser({...user, cart: [...user.cart, product]}))
.catch(error => console.log(error))
}
return (
<UserProvider value={[user, setUser, addToCart, cart]}>
{props.children}
</UserProvider>
)
}
export default UserContextProvider;
And here is the component where I'm trying to use it:
const Cart = props => {
const [user, setUser, cart] = useContext(UserContext)
...
{cart?.length === 0 ? <></> :
<>
{cart?.map(product => {
return(...)
The following code only runs once when the component mounts:
useEffect(() => {
setCart(JSON.parse(localStorage.getItem("cart")) || [])
}, []);
In order to render the latest value you'll have to depend on a prop or something that updates when cart updates:
useEffect(() => {
setCart(JSON.parse(localStorage.getItem("cart")) || [])
}, [props.something]);
Based on your implementation, I'd recommend having your contextProvider provide this data instead of component itself retrieving it from localStorage on each render. You only need the localStorage value when the app starts.

How to make a PATCH request in ReactJS

I'm trying to send a PATCH request to my NodeJS API from my react frontend. I want a situation whereby if you click the edit button, the initial name price appears on the input for necessary editing. Then after editing, you can update it. Displaying the initial data works fine , but saving it doesn't work. I get the 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."
I've looked up the cleanup function, but couldn't make a headway.
Below is my code.
const EditUserForm = () => {
const history = useHistory();
const match = useRouteMatch();
let routeId = match.params.id;
console.log(routeId);
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [item, setItem] = useState({});
const [name, setName] = useState();
const [price, setPrice] = useState();
const handleInputChange = (e) => {
console.log(e.target.value)
const { name, value } = e.target;
setItem({ ...item, [name]: value});
};
const handleSubmit = (e) => {
e.preventDefault();
updateProduct();
history.push('/');
}
const updateProduct = () => {
fetch(`/addproducts/${routeId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: item.name,
price: item.price
}),
})
.then((res) => res.json())
.then((result) => setItem(result))
.catch((err) => console.log('error: ', err))
}
useEffect(() => {
fetch(`/products/${routeId}`, requestOptions)
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setName(result.product.name);
setPrice(result.product.price);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, []);
return (
<form onSubmit={handleSubmit} >
<label>Name</label>
<input
type="text"
name="name"
defaultValue={name}
onChange={handleInputChange}
/>
<label>Price</label>
<input
type="text"
name="price"
defaultValue={price}
onChange={handleInputChange}
/>
<button type="submit">Update</button>
<button>
Cancel
</button>
</form>
)
}
export default EditUserForm
inside "handleSubmit" you are calling "history.push('/')" which produces the error, if you want to change the route then call it in .then of updateProduct
Your useEffect function is applied to each and every rendering and is not cancellable, try rewriting it like this
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
fetch(`/products/${routeId}`, {signal, ...requestOptions})
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setName(result.product.name);
setPrice(result.product.price);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
return controller.abort //so the fetch request can be canceled if the effect is re-executed
}, [routeId]); //only needs to rerun on route change
I am not too sure if the handles submit might cause similar problems, in which case you'd want to do something similar to this.

Can't display data in localhost browser

I creating an app that stores data but when i finish the prompt input i get this error:
Here is my CptList.js
import React, { Component } from 'react';
import { Container, Button } from 'reactstrap';
import uuid from 'uuid';
export default class CpdList extends Component{
state = {}
handleClick = () => {
const date = prompt('Enter Date')
const activity = prompt('Enter Activity')
const hours = prompt('Enter Hours')
const learningStatement = prompt('Enter Learning Statement')
const evidence = prompt('YES! or NO!')
this.setState(state => ({
items: [
...state.items,
{
id: uuid(),
date,
activity,
hours,
learningStatement,
evidence
}
]
}));
}
render() {
const { items } = this.state;
return (
<Container>
<Button
color='dark'
style={{marginBottom: '2rem'}}
onClick={this.handleClick}
>Add Data</Button>
<Button
color='dark'
style={{marginBottom: '2rem'}}
onClick={() => { this.handleClick(items._id) }}
>Delete Data</Button>
</Container>
);
};
};
Can someone please tell me what im doing wrong? I am also having trouble with my delete function, this is my delete coding in my backend:
//Delete a Item
router.delete('/:id', (req, res) => {
Item.findById(req.params.id)
.then(item => item.remove().then(() => res.json({ success: true })))
.catch(err => res.status(404).json({ success: false }));
});
I think you have to initialize state with:
state = { items:[] }
The first time you add item to undefined empty list.
Moreover I think missing a state.items.map somewhere (at least for delete button)
state = [] // convert to array beacuse use map() or other javascipt method
this.setState(state => ({
items: [
// do not speard maybe
{
id: uuid(),
date,
activity,
hours,
learningStatement,
evidence
}
]
}));
plz write handleClick function
tell me working or not

Resources