I'm trying to update my user.cart which is array of objects. When I push new item in cart it's okay till I reload the page. How can I keep the state updated ?
Here is my function:
const {user, setUser} = useContext(UserContext);
const addToCart = (userId, product) => {
fetch(`${API}/cart/usercart`, {
method:"POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify([userId, product])
})
.then(() => {
const newArr = user.cart.concat(product)
setUser(oldState => ({
...oldState,
cart: newArr
}))
})
.catch(error => console.log(error))
}
Here is my UserContext:
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])
return (
<UserProvider value={{user, setUser}}>
{props.children}
</UserProvider>
)
}
export default UserContextProvider;
You need to go through this before the question https://www.freecodecamp.org/news/state-management-with-react-hooks/
Related
I'm successfully updating my plant object to my cluster, but it takes a page reload in order for me to get that updated data. I'm assuming that I may need a useEffect to call my fetch again but I'm unsure how I would do that after my PATCH request.
Does anyone have any suggestions to how I would fetch my updated data after I've updated.
Context
import { createContext, useReducer } from 'react'
export const PlantsContext = createContext()
export const plantsReducer = (state, action) => {
switch(action.type) {
case 'SET_PLANTS':
return {
plants: action.payload
}
case 'CREATE_PLANT':
return {
plants: [action.payload, ...state.plants]
}
case 'DELETE_PLANT':
return {
plants: state.plants.filter((p) => p._id !== action.payload._id)
}
case 'UPDATE_PLANT':
return {
plants: state.plants.map((p) => p._id === action.payload._id ? action.payload : p)
}
default:
return state
}
}
export const PlantsContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(plantsReducer, {
plants: null
})
return (
<PlantsContext.Provider value={{...state, dispatch}}>
{ children }
</PlantsContext.Provider>
)
}
My 'update' function inside PlantDetails component, setting a new water date
const updatePlant = async (e) => {
e.preventDefault()
plant.nextWaterDate = newWaterDate
const response = await fetch("api/plants/" + plant._id, {
method: "PATCH",
body: JSON.stringify(plant),
headers: {
'Content-Type': 'application/json'
}
})
const json = await response.json()
if(response.ok) {
dispatch({ type: "UPDATE_PLANT", payload: json })
}
}
My Home component where that update should render through after PATCH request
const Home = () => {
const { plants, dispatch } = usePlantsContext();
useEffect(() => {
const fetchPlants = async () => {
console.log("called");
// ONLY FOR DEVELOPMENT!
const response = await fetch("/api/plants");
const json = await response.json();
if (response.ok) {
dispatch({ type: "SET_PLANTS", payload: json });
}
};
fetchPlants();
}, [dispatch]);
return (
<div className="home">
<div className="plants">
{plants &&
plants.map((plant) => <PlantDetails key={plant._id} plant={plant} />)}
</div>
<PlantForm />
</div>
);
};
export default Home;
usePlantContext
import { PlantsContext } from "../context/PlantContext";
import { useContext } from "react";
export const usePlantsContext = () => {
const context = useContext(PlantsContext)
if(!context) {
throw Error('usePlantsContext must be used inside an PlantsContext Provider')
}
return context
}
Complete PlantsDetails Component
import { usePlantsContext } from "../hooks/usePlantsContext";
import formatDistanceToNow from "date-fns/formatDistanceToNow";
import { useState } from "react";
import CalendarComponent from "./CalendarComponent";
const PlantDetails = ({ plant }) => {
const [watered, setWatered] = useState(false)
const [newWaterDate, setNewWaterDate] = useState("")
const { dispatch } = usePlantsContext();
const handleClick = async () => {
const response = await fetch("/api/plants/" + plant._id, {
method: "DELETE",
});
const json = await response.json();
if (response.ok) {
dispatch({ type: "DELETE_PLANT", payload: json });
}
};
const updatePlant = async (e) => {
e.preventDefault()
plant.nextWaterDate = newWaterDate
const response = await fetch("api/plants/" + plant._id, {
method: "PATCH",
body: JSON.stringify(plant),
headers: {
'Content-Type': 'application/json'
}
})
const json = await response.json()
if(response.ok) {
dispatch({ type: "UPDATE_PLANT", payload: json })
}
// setWatered(false)
}
return (
<div className="plant-details">
<h4>{plant.plantName}</h4>
<p>{plant.quickInfo}</p>
<p>
{formatDistanceToNow(new Date(plant.createdAt), { addSuffix: true })}
</p>
<span onClick={handleClick}>delete</span>
<div>
<p>next water date: {plant.nextWaterDate}</p>
{/* <input type="checkbox" id="toWater" onChange={() => setWatered(true)}/> */}
<label value={watered} for="toWater">watered</label>
<CalendarComponent setNextWaterDate={setNewWaterDate}/>
</div>
<button onClick={updatePlant}>update</button>
</div>
);
};
export default PlantDetails;
Plant Controller
const updatePlant = async (req, res) => {
const { id } = req.params
if(!mongoose.Types.ObjectId.isValid(id)) {
return res.status(404).json({ error: "No plant" })
}
const plant = await Plant.findByIdAndUpdate({ _id: id }, {
...req.body
})
if (!plant) {
return res.status(400).json({ error: "No plant" })
}
res.status(200).json(plant)
}
Thank you for looking at my question, would appreciate any suggestion.
I want to passing data between components but I have a problem. Not getting any error as long as I don't passing data yet, it's fine. When I try to show the props in the console, I can easily see what I want (history,match,location,AuthStore). But when I try to pass the data, I can only see value and AuthStore in the console and value returns empty. Where am I wrong?
front.layout.js
import Profile from '../../Views/profile'
const Layout = (props) => {
const [user, setUser] = useState({});
const [isLoggedIn, setIsLoggedIn] = useState(false);
props.AuthStore.getToken();
const history = useHistory();
useEffect(() => {
const token =
props.AuthStore.appState != null
? props.AuthStore.appState.user.access_token
: null;
axios
.post(
"/api/authenticate",
{},
{
headers: {
Authorization: "Bearer " + token,
},
}
)
.then((res) => {
if (!res.data.isLoggedIn) {
history.push("/login");
}
setUser(res.data.user);
setIsLoggedIn(res.data.isLoggedIn);
})
.catch((error) => {
history.push("/login");
});
}, []);
return (
<>
<Profile value={user} />
</>
)
index.js
const Profile = (props) => {
console.log(props);
const { params } = props.match;
const [data, setData] = useState({});
const history = useHistory();
if(props.location.key){
useEffect(() => {
axios
.get(
`/api/${params.username}`,
{
headers: {
Authorization:
"Bearer " +
props.AuthStore.appState.user.access_token,
},
}
)
.then((res) => {
if (res.data.username) {
setData(res.data);
}
})
.catch((error) => {
console.log(error);
});
}, []);
}
I'm using an axios call to a database to get "about me" data, for client to update. DB is connected properly, as I am able to log in just fine, I've isolated this issue pretty well to my GET request.
My context provider file:
import React, { useState } from 'react'
import axios from 'axios'
export const UserContext = React.createContext()
const userAxios = axios.create()
userAxios.interceptors.request.use((config) => {
const token = localStorage.getItem("token")
config.headers.Authorization = `Bearer ${token}`
return config
})
const UserProvider = (props) => {
const initState = {
user: JSON.parse(localStorage.getItem("user")) || {},
token: localStorage.getItem("token") || "",
authErrMsg: ""
}
const [userState, setUserState] = useState(initState)
const [dataState, setDataState] = useState({
bioData: []
})
const login = credentials => {
axios.post("/auth/login", credentials)
.then(res => {
const { user, token } = res.data
localStorage.setItem("user", JSON.stringify(user))
localStorage.setItem("token", token)
setUserState(res.data)
})
.catch(err => handleAuthErr(err.response.data.errMsg))
}
const handleAuthErr = errMsg => {
setUserState(prevUserState => ({
...prevUserState,
authErrMsg: errMsg
}))
}
const logout = () => {
localStorage.removeItem("token")
localStorage.removeItem("user")
setUserState({
user: {},
token: "",
authErrMsg: ""
})
}
const getData = () => {
axios.get('/info/bio')
.then(res => {
setDataState(prevData => ({
...prevData,
bioData: res.data
}))
})
.catch(err => {
console.log(err)
})
}
const deleteBio = (id) => {
userAxios.delete(`/api/bio/${id}`)
.then(res => {
setDataState(prevData => ({
...prevData,
bioData: dataState.bioData.filter(bio => bio._id !== id)
}))
})
.catch(err => console.log(err.response.data.errMsg))
}
const addBio = (newText) => {
const newBio = {
bioText: newText
}
userAxios.post('/api/bio', newBio)
.then(res => {
getData()
})
.catch(err => console.log(err))
}
const editBio = (update, id) => {
const updatedBio = {
bioText: update
}
userAxios.put(`/api/bio/${id}`, updatedBio)
.then(res => {
console.log(res.data, 'edited')
getData()
})
.catch(err => console.log(err))
}
return (
<UserContext.Provider
value={{
user: userState.user,
token: userState.token,
authErrMsg: userState.authErrMsg,
login: login,
logout: logout,
getData: getData,
dataState: dataState,
editBio: editBio,
deleteBio: deleteBio,
addBio: addBio
}}>
{props.children}
</UserContext.Provider>
)
}
export default UserProvider
Here's my Bio component. The loading effect never changes because for some reason, no "bioData" is saving, in the provider. Tested it with that little button/handleClick and coming up an empty array.
import React, {useContext, useState, useEffect} from 'react'
import { UserContext } from './context/userProvider'
const Bio = () => {
const { token, editBio, dataState: {bioData} } = useContext(UserContext)
const [loader, setLoader] = useState('Loading')
useEffect(() => {
if(bioData[0]?._id === undefined){
setLoader('Loading')
}else {
setLoader(bioData[0]?._id)
}
})
// let initText = bioData[0].bioText
const [bioText, setBioText] = useState("initText")
const handleChange = (e) => {
setBioText(e.target.value)
}
const handleUpdate = () => {
editBio(bioText, bioData[0]._id)
alert`Bio successfully updated. :)`
}
const handleClick = () => {
console.log(bioData)
}
return (
<div className='bio'>
<h1>About Me</h1>
<div className='bio-content'>
{loader === 'Loading' ?
<div>
<p>Loading...</p>
<button onClick={handleClick}>thing</button>
</div>
:
<>
{token ?
<div className="editBio">
<p>edit mee</p>
</div>
:
<h4>{bioData[0].bioText}</h4> }
</>
}
</div>
</div>
)
}
export default Bio
Thanks in advance guys! Let me know if I can post routes or anything that might be helpful.
I'm trying to migrate to redux-thunk I have few issues here.
Previously, I had fetch response data which is saved in state, and with that state I used filter to save other states in the app.
loadMeetingRoomData = async () => {
try {
const getMeetingRoomData = await fetch(URL, { headers: myHeaders });
const responseJson = await getMeetingRoomData.json();
if (responseJson.length === 0) {
this.setState({
meetingRoomStatus: false,
})
} else {
this.setState({
meetingRoomData: responseJson,
});
const inProgressMeeting = responseJson.filter(obj => {
return obj.Status === INPROGRESS_CODE
});
this.setState({
meetingRoomInProgressCount: inProgressMeeting.length,
});
const upcomingMeeting = responseJson.filter(obj => {
return obj.Status === UPCOMING_CODE_MORE_THAN_30_MIN || obj.Status === UPCOMING_CODE_LESS_THAN_30_MIN;
});
this.setState({
meetingRoomUpcomingCount: upcomingMeeting.length,
})
const finishedMeeting = responseJson.filter(obj => {
return obj.Status === FINISHED_CODE
})
this.setState({
meetingRoomFinishedCount: finishedMeeting.length,
})
}
}
catch (err) {
console.log(new Error(err));
}
}
And below is the code, My question is where do I filter the reponseJson to
update the new three states in the previous code ( meetingRoomInProgressCount, meetingRoomUpComingCount, meetingRoomFinished)
Do I have to dispatch it in the component file using mapDispatchToProps or
use middleware to update the store , or filter inside the reducer?
#Action Creator
export const fetchMeetingRoom = () => {
return function(dispatch){
return fetch(URL, { headers: myHeaders })
.then(response => response.json())
.then(json =>
dispatch({ type: 'FETCH_MEETINGROOM_SUCCESS' ,payload : json})
)
}
}
export default function reducer( state = initialState , action){
switch(action.type) {
case FETCH_MEETINGROOM_SUCCESS :
let value = action.payload;
if(value.length === 0){ return {...state,meetingRoomStatus :false,}}
else{
return {...state,
isLoading : false,
meetingRoomData : action.payload}
}
default :
return state;
}
}
I use this structure for my projects
actions:
import { TYPE_VARS } from "./type";
export const func_name = () => (dispatch) => {
return fetch(URL, {
method: "GET",
headers: {
"Content-Type": "application/json",
}
})
.then((res) => res.json())
.then((data) => {
dispatch({
type: TYPE_VARS,
payload: data,
});
return data;
})
.catch((err) => console.log(err.message));
};
reducer:
import { TYPE_VARS } from "../actions/type";
const initialState = {
my_state: {},
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case TYPE_VARS:
return {
...state,
my_state: payload,
};
default:
return state;
}
}
component:
// call action where do you need
func_name()
const mapStateToProps = state => ({
my_state: state.reducerFileName.my_state
})
export default connect(mapStateToProps, { func_name })(ComponentName)
I keep encountering an infinity loop when trying to use the useEffect hook to fetch and set data. ive tried 3 variations of the hook and they all produce a loop, how do i stop this from happening?
useEffect(() => {
(async () => {
PostApi.getPostsByUser(auth.user._id, auth.token).then(response => setPosts(response))
})()
})
useEffect(() => {
(async () => {
PostApi.getPostsByUser(auth.user._id, auth.token).then(response => setPosts(response))
})()
},[])
useEffect(() => {
(async () => {
PostApi.getPostsByUser(auth.user._id, auth.token).then(response => setPosts(response))
})()
},[profile.posts])
EDIT: Here is the PostApi.getPostsByUser code
getPostsByUser: (userId, token) => {
return(
axios
.get("/api/posts/by/" + userId, {
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + token
}
})
.then(response => {
console.log("Posts by User");
console.log(response.data);
return response.data;
})
.catch(err => console.log(err))
)
}
EDIT: Function component code:
const Posts = () => {
const [{auth}] = useAuth();
const [{profile},, setPosts] = useProfile()
useEffect(() => {
PostApi.getPostsByUser(auth.user._id, auth.token)
.then(response => setPosts(response));
},[]);
console.log(profile)
return(
<div className="User-Post">
<div className="New-Post">
<NewPost />
</div>
<div className="User-Posts-Content">
{
profile.posts ? profile.posts.map((item, key) => {
return <Post post={item} key={key} />
}) : null
}
</div>
</div>
)
}
export default Posts
Change:
const [auth] = useAuth();
const [profile, setPosts] = useState();
const setPosts = posts => { setPosts(state => ({ ...state, profile: {
...state.profile, posts: posts } })) }
getPostsByUser: (userId, token) => {
return(
axios
.get("/api/posts/by/" + userId, {
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + token
}
});
}
and
useEffect(() => {
PostApi.getPostsByUser(auth.user._id, auth.token)
.then(response => setPosts(response.data));
},[]);
You can try like this.
useEffect(() => {
const get = async () => {
const response = await PostApi.getPostsByUser(auth.user._id, auth.token);
setPosts(response);
}
get();
},[]);
This works for me ... and the simplest solution too
const [toggle, setToggle] = useState(false);
useEffect(() => {
(async () => {
PostApi.getPostsByUser(auth.user._id, auth.token).then(response => setPosts(response))
})()
},toggle)