setting state not working in react in a weird way - reactjs

I made a hook to interact with my api:
import { useContext, useCallback } from "react";
import { toastContext } from "../trds";
export default function useAPIHook(){
const launchToast = useContext(toastContext);
const returnFunction = useCallback(async ({
user, url, fetchOptions = {}, okCallback = () => {}, failedCallback = () => {}
}) => {
const idToken = await user.getIdToken();
const apiResponse = await fetch(`${process.env.REACT_APP_BACKEND_URL}${url}?idToken=${idToken}`, fetchOptions);
const apiReturn = await apiResponse.json();
if(apiResponse.ok) okCallback(apiReturn);
else{
if(apiResponse.status === 400)
launchToast(apiReturn.error, 'error');
else{
console.log(apiReturn.error);
launchToast('Something went wrong. Contact the developer!', 'error');
}
failedCallback();
}
}, [launchToast]);
return returnFunction;
}
and then the users hook:
import { useContext, useState, useEffect } from "react";
import { userContext } from "../contexts/user";
import { toastContext } from "../trds";
import useAPIHook from "./useAPIHook";
export default function useUsers(){
const currentUser = useContext(userContext);
const api = useAPIHook();
const launchToast = useContext(toastContext);
const [users, setUsers] = useState([]);
useEffect(() => {
(async () => {
await api({
user: currentUser,
url: '/users',
okCallback: (returnedUsers) => {
setUsers(returnedUsers);
},
failedCallback: () => {
launchToast('Failed while fetching users!', 'error')
}
});
})();
}, [api, currentUser, launchToast]);
const createNewUser = async (userData) => {
await api({
user: currentUser,
url: '/users',
fetchOptions:{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(userData)
},
okCallback: (newUser) => {
setUsers(prevState => [...prevState, newUser]);
launchToast('New user has been created!', 'success')
},
failedCallback: () => {
launchToast('Failed while creating user!', 'error')
}
});
}
const updateUser = async (userData) => {
await api({
user: currentUser,
url: `/users/${userData._id}`,
fetchOptions:{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(userData)
},
okCallback: (updatedUser) => {
setUsers(prevUsers => {
return prevUsers.map(user => user._id === updatedUser._id ? updateUser : user);
});
launchToast('User has been updated!', 'success')
},
failedCallback: () => {
launchToast('Failed while updating user!', 'error')
}
});
}
const deleteUser = async (userData) => {
await api({
user: currentUser,
url: `/users/${userData._id}`,
fetchOptions:{
method: 'DELETE',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(userData)
},
okCallback: (deletedUser) => {
setUsers(prevUsers => {
return prevUsers.filter(user => user._id !== deletedUser._id);
});
launchToast('User has been deleted!', 'success');
},
failedCallback: () => {
launchToast('Failed while deleting user!', 'error')
}
});
}
return { users, createNewUser, updateUser, deleteUser }
}
my problem is that in the createNewUser function at okCallback, the state wont update with the newUser pushed to the state. nothing happens, like no errors, nothing. I debugged newUser 100 times and its good. setting the users state in the other functions (updateUser, deleteUser) is working properly. does anybody have an idea what could it be?
UPDATE: its not working in updateUser neither, only works in deleteUser
UPDATE 2: the updateUser and createNewUser are being called in my user form modal:
import { useEffect, useState, useContext, useCallback } from "react";
import { Button, Modal, toastContext } from "../trds";
import useUsers from "../hooks/useUsers";
const initialState = {
email: '',
name: '',
role: 'viewer'
};
export default function UserFormModal({userData, isOpen, onClose}){
const launchToast = useContext(toastContext);
const { createNewUser, updateUser } = useUsers();
const [data, setData] = useState(initialState);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if(userData) setData(userData);
}, [userData]);
const handleSubmit = async () => {
if(!data.email || !data.name){
return launchToast('You have to provide email and name!', 'error');
}
setIsLoading(true);
if(userData){
await updateUser(data)
} else {
await createNewUser(data);
}
setData(initialState);
onClose();
setIsLoading(false);
}
const handleClose = useCallback(() => {
setData(initialState);
onClose();
}, [onClose]);
return(
<Modal
title={userData ? 'Update user' : 'Create user'}
isOpen={isOpen}
onClose={handleClose}
>
<trds-stack style={{gap: 'var(--space--l)'}}>
<input
type="email"
name="userEmail"
placeholder="Email address"
value={data.email}
onChange={(e) => setData(prevState => ({...prevState, email: e.target.value}))}
/>
<input
type="text"
name="userName"
placeholder="Name"
value={data.name}
onChange={(e) => setData(prevState => ({...prevState, name: e.target.value}))}
/>
<trds-stack style={{gap: 0}}>
<label>Role</label>
<select
name="userRole"
onChange={(e) => setData(prevState => ({...prevState, role: e.target.value}))}
value={data.role}
>
<option value="viewer">Viewer</option>
<option value="technician">Technician</option>
<option value="admin">Admin</option>
</select>
</trds-stack>
<Button
text={userData ? 'Update user' : 'Create user'}
loading={isLoading}
onClick={handleSubmit}
/>
</trds-stack>
</Modal>
)
}
SO, it may have correlation...

Related

Update my state after PATCH request - useReducer, context

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.

User is logged in and X-XSRF-TOKEN is passed in the headers but just displaying 401 Unauthorized in Laravel Sanctum & NextJs

I am developing a NextJs app with a Laravel backend. I chose to go with Token based authentication. Logging in and fetching user data via Postman is working fine. But it's only returning 401 Unauthorized on the NextJs app for fetching user data even though logging in is fine.
I used this https://github.com/laravel/breeze-next as an example. I implemented the same as it has.
This is the error message that I received on console log.
GET http://localhost:8000/api/v1/auth-user 401 (Unauthorized)
CODES
axios.js
import Axios from 'axios';
const axios = Axios.create({
baseURL: process.env.NEXT_PUBLIC_BACKEND_URL,
headers: {
'X-Requested-With': 'XMLHttpRequest',
},
withCredentials: true,
});
export default axios;
useAuth.js
import useSWR from 'swr';
import axios from '../lib/axios';
import { useEffect } from 'react';
import { useRouter } from 'next/router';
export const useAuth = ({ middleware, redirectIfAuthenticated } = {}) => {
const router = useRouter();
const {
data: user,
error,
mutate,
} = useSWR('/api/v1/auth-user', () =>
axios
.get('/api/v1/auth-user')
.then((res) => res.data)
.catch((error) => {
if (error.response.status !== 409) throw error;
router.push('/verify-email');
})
);
const csrf = () => axios.get('/sanctum/csrf-cookie');
const register = async ({ setErrors, ...props }) => {
await csrf();
setErrors([]);
axios
.post('/register', props)
.then(() => mutate())
.catch((error) => {
if (error.response.status !== 422) throw error;
setErrors(error.response.data.errors);
});
};
const login = async ({ setErrors, setStatus, ...props }) => {
await csrf();
setErrors([]);
setStatus(null);
axios
.post('/login', props)
.then(() => mutate())
.catch((error) => {
if (error.response.status !== 422) throw error;
setErrors(error.response.data.errors);
});
};
const forgotPassword = async ({ setErrors, setStatus, email }) => {
await csrf();
setErrors([]);
setStatus(null);
axios
.post('/forgot-password', { email })
.then((response) => setStatus(response.data.status))
.catch((error) => {
if (error.response.status !== 422) throw error;
setErrors(error.response.data.errors);
});
};
const resetPassword = async ({ setErrors, setStatus, ...props }) => {
await csrf();
setErrors([]);
setStatus(null);
axios
.post('/reset-password', { token: router.query.token, ...props })
.then((response) =>
router.push('/login?reset=' + btoa(response.data.status))
)
.catch((error) => {
if (error.response.status !== 422) throw error;
setErrors(error.response.data.errors);
});
};
const resendEmailVerification = ({ setStatus }) => {
axios
.post('/email/verification-notification')
.then((response) => setStatus(response.data.status));
};
const logout = async () => {
if (!error) {
await axios.post('/logout').then(() => mutate());
}
window.location.pathname = '/login';
};
useEffect(() => {
if (middleware === 'guest' && redirectIfAuthenticated && user)
router.push(redirectIfAuthenticated);
if (window.location.pathname === '/verify-email' && user?.email_verified_at)
router.push(redirectIfAuthenticated);
if (middleware === 'auth' && error) logout();
}, [user, error]);
return {
user,
register,
login,
forgotPassword,
resetPassword,
resendEmailVerification,
logout,
};
};
login.js
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { useAuth } from '../hooks/useAuth';
const LoginScreen = () => {
const [email, setEmail] = useState(null);
const [password, setPassword] = useState(null);
const [shouldRemember, setShouldRemember] = useState(false);
const [errors, setErrors] = useState([]);
const [status, setStatus] = useState(null);
const router = useRouter();
const { login, user } = useAuth({
middleware: 'guest',
redirectIfAuthenticated: '/',
});
useEffect(() => {
if (router.query.reset?.length > 0 && errors.length === 0) {
setStatus(atob(router.query.reset));
} else {
setStatus(null);
}
}, [errors, router]);
const submitHandler = (e) => {
e.preventDefault();
login({ email, password, remember: shouldRemember, setErrors, setStatus });
console.log(email, password);
};
return (
<div>
<form onSubmit={submitHandler}>
<input type="email" onChange={(e) => setEmail(e.target.value)} />
<br />
<input type="password" onChange={(e) => setPassword(e.target.value)} />
<br />
<button type="submit">Sign In</button>
</form>
</div>
);
};
export default LoginScreen;

PATCH request seems like a step behind

Hey folks really hope someone can help me here. I'm successfully updating my object in my mongo cluster, it updates but it does not render that update straight away to the browser. It will only update after a reload or when I run my update function again, it doesn't fetch that update straight away and I can't understand why. Does anyone have any suggestions?
I'm using context and reducer.
PlantDetails
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 })
}
console.log('updated')
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 onChange={(e) => setNewWaterDate(e.target.value)}/>
<button onClick={updatePlant}>update</button>
<input value={watered} type="checkbox" id="toWater" onChange={() => setWatered(true)}/>
<label for="toWater">watered</label>
{watered && <CalendarComponent updatePlant={updatePlant} setNextWaterDate={setNewWaterDate}/>}
</div>
</div>
);
};
export default PlantDetails;
Context which wraps my
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 plantController (update)
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)
}
Home component
import { useEffect } from "react";
import PlantDetails from "../components/PlantDetails";
import PlantForm from "../components/PlantForm";
import CalendarComponent from "../components/CalendarComponent";
import { usePlantsContext } from "../hooks/usePlantsContext";
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;
Any help would be greatly appreciated.
My patch requests were going through smoothly but my state would not update until I reloaded my page. It was not returning the document after the update was applied.
https://mongoosejs.com/docs/tutorials/findoneandupdate.html#:~:text=%3B%20//%2059-,You,-should%20set%20the

I can't set a state from fetch in React

I'm trying to sign in and setting the current user. The problem is that the login is successful, the data is correct but I can't set the state, the user is empty.
UserContext.js
import React, { useContext, useState } from 'react';
const UserContext = React.createContext();
export const useAuth = () => {
return useContext(UserContext);
}
export const UserContextProvider = ( {children} ) => {
const [ user, setUser ] = useState({
name: '',
lastname: '',
username: '',
password: ''
});
const [ validation, setValidation ] = useState({
username: '',
password: ''
});
const setUserData = (e) => {
return ( {target: {value}} ) => {
setUser(data => ( {...data, [e]: value} ));
}
}
const setUserValidation = (e) => {
return ( {target: {value}} ) => {
setValidation(data => ( {...data, [e]: value} ));
}
}
const signUp = async () => {
return await fetch('http://localhost:8080/users/signup', {
method: 'POST',
body: JSON.stringify(user),
headers: { 'Content-Type': 'application/json' }
});
}
const signIn = async () => {
return await fetch('http://localhost:8080/users/signin', {
method: 'POST',
body: JSON.stringify(validation),
headers: { 'Content-Type': 'application/json' }
}).then((res) => res.json())
.then(data => {
console.log(data);
setUser({
name: data.name,
lastname: data.lastname,
username: data.username,
password: data.password
});
console.log(user);
});
}
const signOut = async () => {
await fetch('http://localhost:8080/users/signout');
setUser(null);
return;
}
return (
<UserContext.Provider value={{
user,
setUserData,
setUserValidation,
signUp,
signIn,
signOut
}}>
{ children }
</UserContext.Provider>
);
}
SignIn.js
import './SignIn.css';
import { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '../../context/UserContext';
const SignIn = () => {
const { signIn, setUserValidation, user } = useAuth();
const [ errorMessage, setErrorMessage ] = useState(null);
const navigate = useNavigate();
const handleSubmit = async (e) => {
e.preventDefault();
setErrorMessage(null);
signIn().then(() => {
console.log(user);
setErrorMessage(null);
navigate('/');
}).catch(err => {
setErrorMessage('Error singing in, please try again.', err);
});
}
return (
<div className='SignIn'>
<h3>Sign In</h3>
<form className='LoginForm' onSubmit={handleSubmit}>
{ errorMessage && <h4 className='ErrorMessage'>{errorMessage}</h4> }
<input type='email' name='username' placeholder='Email' onChange={setUserValidation('username')} required/>
<input type='password' name='password' placeholder='Password' onChange={setUserValidation('password')} required/>
<button className='Login' type='submit'>Sign In</button>
</form>
<h5>Don't have an account?</h5><Link className='Redirect' to='/signup'>Sign Up</Link>
</div>
);
}
export default SignIn;
As you can see, the first console.log shows the correct user information, but then is empty after the setUser() and in the SignIn.js component.
This is a normal behavior. You can't access state directly after set state. The updated state will only be available on next render. So do about context.
This is what you can do if you need to access user data directly after SignIn().
UserContext.js
const signIn = async () => {
return await fetch('http://localhost:8080/users/signin', {
method: 'POST',
body: JSON.stringify(validation),
headers: { 'Content-Type': 'application/json' }
}).then((res) => res.json())
.then(data => {
console.log(data);
const usr = {
name: data.name,
lastname: data.lastname,
username: data.username,
password: data.password
}
setUser(usr);
// console.log(user); <-- You can't do this
return usr // <--
});
}
SignIn.js
const handleSubmit = async (e) => {
e.preventDefault();
setErrorMessage(null);
signIn().then((usr) => { // <---
console.log(usr);
// console.log(user); <-- You can't do this
setErrorMessage(null);
navigate('/');
}).catch(err => {
setErrorMessage('Error singing in, please try again.', err);
});
}
I guess what could be happening here. I have no time to test my guess, so please forgive me if I'm saying something wrong.
When you change a state in a context, the provider and its children are re-rendered. In this case, the UserContextProvider and its children.
So first thing, please be sure that SignIn is rendered inside a UserContextProvider, e.g. embedding all the app inside a . I generally do this in the index.js file.
ReactDOM.render(
<UserContextProvider>
{/* ... app here, that includes SignIn ... */}
</UserContextProvider>,
document.getElementById('root')
);
Second thing, since you are including the console.log() so that are executed in the same rendering in which you change the state, it's clear that they won't reflect the new value that will be available in the successive rendering only.
I suggest that you put the console.log(user) at the beginning of the SignIn component, say immediately after useNavigate(), outside the handleSubmit function.
const SignIn = () => {
const { signIn, setUserValidation, user } = useAuth();
const [ errorMessage, setErrorMessage ] = useState(null);
const navigate = useNavigate();
console.log(user)
// ...ecc...
}
If I'm right, this console.log will be executed (at least) twice, one for the initial rendering, one for the subsequent rendering triggered by setUser (you can also include a console.log in the handleSubmit just to detect the re-rendering triggered by setUser). In the last rendering, you should see the user data.
If this works as I expect, I guess that you can handle the signIn with something like this
const SignIn = () => {
const { signIn, setUserValidation, user } = useAuth();
const [ errorMessage, setErrorMessage ] = useState(null);
const navigate = useNavigate();
// in the first rendering, the userName will be '', so it won't navigate
// if the component is re-rendered after the setUser in signIn,
// in this rendering there will be a userName, hence the navigation will proceed
if (user.userName !== '') {
navigate('/');
}
const handleSubmit = async (e) => {
e.preventDefault();
setErrorMessage(null);
signIn().catch(err => {
setErrorMessage('Error singing in, please try again.', err);
});
}
return (
// ... as before ...
);
}
Happy coding! - Carlos

Trying to setState with React Hooks, using axios.. Not getting data

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.

Resources