I can't set a state from fetch in React - reactjs

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

Related

setting state not working in react in a weird way

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...

How to pass data from parent to child (react Modal)?

I have a page users.jsx (parent) and a component DialogEditUser.jsx (child) and i would like to pass a specific data of a user that is located in parent to child by it's id (using find method)
This passed data should be loaded to its input in react modal as a value.
users.jsx Code:
import React, { useState, useEffect } from 'react'
import DialogAddUser from 'src/components/DialogAddUser'
import { getUsers} from 'src/Service/api'
const Typography = () => {
const [users, setUsers] = useState([])
useEffect(() => {
getAllUsers()
}, [])
const deleteUserData = async (id) => {
setConfirmDialog({
...setConfirmDialog,
isOpen: false,
})
await deleteUser(id)
getAllUsers()
setNotify({
isOpen: true,
message: 'Article Deleted Successfully.',
type: 'error',
})
}
const getAllUsers = async () => {
let response = await getUsers()
setUsers(response.data)
console.log(response.data)
}
return ( //... )
DialogEditUsers.jsx Code:
import { useEffect, useState } from 'react'
import { getUsers, editUser } from '../Service/api'
const initialValue = {
id: '',
code: '',
article: '',
price: '',
vat: '',
status: '',
company_id: '',
}
export default function DialogAddUser() {
const [user, setUser] = useState(initialValue)
const { code, article, price, vat, status, company_id } = user
const normalize = (v) => ({
code: v.code,
article: v.article,
price: Number(v.price),
vat: Number(v.vat),
status: Number(v.status),
company_id: Number(v.company_id),
})
useEffect(() => {
loadUserDetails()
}, [])
const loadUserDetails = async () => {
const response = await getUsers(id)
console.log('loading user details ', response)
setUser(response.data.find((x) => x.id == id))
}
const editUserDetails = async () => {
const response = await editUser(id, normalize(user))
console.log('Edit user details ', response)
}
const onValueChange = (e) => {
console.log(e.target.value)
setUser({ ...user, [e.target.name]: e.target.value })
}
return (
<>
<CModal
visible={visible}
onClose={() => setVisible(false)}
backdrop={'static'}
keyboard={false}
portal={false}
>
<CModalHeader>
<CModalTitle>Edit Article:</CModalTitle>
</CModalHeader>
<CModalBody>
<CForm>
<CFormInput
type="text"
id="exampleFormControlInput1"
label="Code :"
placeholder="Enter Code"
text=" "
aria-describedby="exampleFormControlInputHelpInline"
onChange={(e) => onValueChange(e)}
value={code}
name="code"
/>
<CFormInput
type="text"
id="exampleFormControlInput2"
label="Article :"
placeholder="Enter Article"
text=" "
aria-describedby="exampleFormControlInputHelpInline"
onChange={(e) => onValueChange(e)}
value={article}
name="article"
/>
//...the rest of inputs...
api.js Code:
import axios from 'axios'
const baseURL = 'https://api.factarni.tn/article'
const token =
'eyJhbGciOiJSUzI1NiIsImtpZCI6IjIxZTZjMGM2YjRlMzA5NTI0N2MwNjgwMDAwZTFiNDMxODIzODZkNTAiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiZmFraHJpIGtyYWllbSIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BSXRidm1uMS12dWJJcHNxTURKMkNTcDhVcTlmU3I1LUI1T3Y3RHY2SFRNMT1zMTMzNyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9mYWN0YXJuaSIsImF1ZCI6ImZhY3Rhcm5pIiwiYXV0aF90aW1lIjoxNjYzNzY3ODk5LCJ1c2VyX2lkIjoiaWhqM0JWM0hIRFhpVnUwdmpzV3ZidjMyRDdMMiIsInN1YiI6ImloajNCVjNISERYaVZ1MHZqc1d2YnYzMkQ3TDIiLCJpYXQiOjE2NjM3Njc4OTksImV4cCI6MTY2Mzc3MTQ5OSwiZW1haWwiOiJmYWtocmlpLmtyYWllbUBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJnb29nbGUuY29tIjpbIjEwODU1MTA3MjAwODIwNjMxMjI0NCJdLCJlbWFpbCI6WyJmYWtocmlpLmtyYWllbUBnbWFpbC5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJnb29nbGUuY29tIn19.bvRTxHfPtJrQjF2BjXqhs7ji738kma55LMFVRb8jkeraWP-JRBi-LRPa0d7OR_-BPwCGuRBXIb6980_PP8wjhBeDdB5B77GujiGn3nUvpPOFeIaM0L7muw1NKo4YCtS3v6ifuywypTbL3_5x3SBFZEH-QV0sp5DAzaA-P3Fn8AwP66o3cUPHGengGpZNsfkJ0FYcqzH-xpyKVVWV'
//i dont mind sharing this token, it's for you to test this code if you need.
const config = { headers: { Authorization: `Bearer ${token}` } }
export const getUsers = async (id) => {
id = id || ''
try {
return await axios.get(`${baseURL}`, config)
} catch (error) {
console.log('Error while calling getArticles api ', error)
}
}
export const editUser = async (id, user) => {
return await axios.put(`${baseURL}/${id}`, user, config)
}
The only node error i'm getting in terminal using this code above (because i dont know how to pass the proper id of specified user) is:
src\components\DialogEditUser.jsx
Line 45:37: 'id' is not defined no-undef
Line 47:47: 'id' is not defined no-undef
Line 51:37: 'id' is not defined no-undef
For better explanation the problem (i dont know how to use online snippets sorry):
So what i'm expecting is: When i click on Edit button, i should get a modal with form that are filled with user data (code, article, price, vat, status and company_id) in each input of the form as value, just like this gif below:
Also, console.log(response.data) in users page shows this:
few days back i also faced the same issue. Solution for me is to create state in parent component and pass state to child. Example for it-
Parent Class
const parent= ()=>{
const [name, setName]= useState('')
const [password, setPassword]= useState('')
return(
<Child setName={setName} setPassword={setPassword} />
)
}
Child Class
const Child = ({setPassword,setName})=>{
return(
<div>
<input type="text" placeholder="Enter Name" onChange={(e)=>setPassword(e.target.value)} />
<input type="text" placeholder="Enter Name" onChange={(e)=>setPassword(e.target.value)} />
</div>
)
}
Hope my answer will help you to solve your problem, if you still facing issue, lemme know i will help you.
In users.jsx, pass props of (user.id):
<DialogEditArticle props={user.id} />
Then, in DialogEditArticle.jsx, create a new data and call in it props:
const DialogEditArticle = (data) => {
console.log(data.props)
Now console.dev, you will get all the ids of user in database (because button edit is inside map function).
Result:

Weird CORS issue with login in React?

I have a small issue here. Basically I am trying to work on a login/register system built in React.
This is how I handle the signup:
const submitHandler = async (data: object) => {
console.log(data);
await fetch("http://localhost:4000/signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
})
.then((res) => res.json())
.then((data) => {
if (data.status_code === "SUCCESS") {
router.push("/login");
}
})
.catch((err) => console.log(err));
};
That works perfectly fine and also saves the data in the database after signing up, but my login has some issues not handling the user (redirecting him)
const submitHandler = async (data: object) => {
await fetch("http://localhost:4000/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
})
.then((res) => res.json())
.then((data) => {
if (data.status_code === "SUCCESS") {
localStorage.setItem("userData", JSON.stringify(data.data));
router.push("/dashboard");
} else {
setError("Invalid Credentials!");
}
})
.catch((err) => console.log(err));
};
When I enter the correct data from the signup, nothing really happens (it should set the jwt token into the localstorage and then redirect me to the dashboard route), any ideas?
This is not redirecting you to login route because if you clearly look at bottom of your signup request handler where you are comparing data.status_code === " SUCCESS" that should be data.status_code === "SUCCESS".
"SUCCESS" & " SUCCESS" both are different.
For me it seems like that you have a typo when you check the data.status_code. You misspelled " SUCCESS" with "SUCCESS". If that was the case, then you can move on, but here is an alternate solution you could use. I also provided a back-end file (I used Node.js with Express).
import React, { useState } from 'react';
import PropTypes from 'prop-types';
async function submitHandler(credentials) {
return fetch('http://localhost:4000/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(credentials)
})
.then(data => data.json())
}
export default function Login({ setToken }) {
const [username, setUserName] = useState();
const [password, setPassword] = useState();
const handleSubmit = async e => {
e.preventDefault();
const token = await submitHandler({
username,
password
});
setToken(token);
}
return(
<div className="login-wrapper">
<h1>Please Log In</h1>
<form onSubmit={handleSubmit}>
<label>
<p>Username</p>
<input type="text" onChange={e => setUserName(e.target.value)} />
</label>
<label>
<p>Password</p>
<input type="password" onChange={e => setPassword(e.target.value)} />
</label>
<div>
<button type="submit">Submit</button>
</div>
</form>
</div>
)
}
Login.propTypes = {
setToken: PropTypes.func.isRequired
};
useToken.js - contains the logic for the custom hook
import { useState } from 'react';
export default function useToken() {
const getToken = () => {
const tokenString = localStorage.getItem('token');
const userToken = JSON.parse(tokenString);
return userToken?.token
};
const [token, setToken] = useState(getToken());
const saveToken = userToken => {
localStorage.setItem('token', JSON.stringify(userToken));
setToken(userToken.token);
};
return {
setToken: saveToken,
token
}
}
App.js
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import './App.css';
import Dashboard from '../Dashboard/Dashboard';
import Login from '../Login/Login';
import Preferences from '../Preferences/Preferences';
import useToken from './useToken';
function App() {
const { token, setToken } = useToken();
if(!token) {
return <Login setToken={setToken} />
}
return (
<div className="wrapper">
<h1>Application</h1>
<BrowserRouter>
<Switch>
<Route path="/dashboard">
<Dashboard />
</Route>
<Route path="/preferences">
<Preferences />
</Route>
</Switch>
</BrowserRouter>
</div>
);
}
export default App;
server.js
const express = require('express');
const cors = require('cors')
const app = express();
app.use(cors());
app.use('/login', (req, res) => {
res.send({
token: 'test123'
});
});
app.listen(8080, () => console.log('API is running on http://localhost:4000/login'));

Firebase store document to variable after login

I am trying to do backend for my website and I have login, signup, reset password etc. all working. What I am trying to do now is when user log in or sign up, AuthContext to check for file that match his UID and if exist store it to variable or if not exist create it and store it to variable. Just cant get it work. Best i got so far is code at bottom but problem there is when I log out user I am getting all sort errors because user not exists anymore.
my context file look like this so far and everything is working:
import { createContext, useContext, useEffect, useState } from "react";
import { auth, db } from "../firebase";
export const AuthContext = createContext();
export const useAuth = () => {
return useContext(AuthContext);
};
const AuthContextProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const [currentUserDoc, setCurrentUserDoc] = useState(null);
const [loading, setLoading] = useState(true);
const signup = (email, password) => {
return auth.createUserWithEmailAndPassword(email, password);
};
const login = (email, password) => {
return auth.signInWithEmailAndPassword(email, password);
};
const logout = () => {
return auth.signOut();
};
const resetPassword = (email) => {
return auth.sendPasswordResetEmail(email);
};
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const value = { currentUser, currentUserDoc, signup, login, logout, resetPassword };
return <AuthContext.Provider value={value}>{!loading && children}</AuthContext.Provider>;
};
export default AuthContextProvider;
I tryed to change useEffect hook to this but can't get it done right:
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async (user) => {
setCurrentUser(user);
const userDoc = db.collection("users").doc(user.uid);
await userDoc.get().then((doc) => {
if (doc.exists) {
setCurrentUserDoc(doc.data());
} else {
doc.set({
email: user.email,
first_name: "",
last_name: "",
country: "",
organization: "",
});
}
});
setLoading(false);
});
return unsubscribe;
}, []);
This is error when there is no user logged in:
Code where I am trying to use it:
import styles from "./Header.module.scss";
import { useAuth } from "../../contexts/AuthContext";
const Header = () => {
const { logout, currentUserDoc } = useAuth();
return (
<header className={styles.header}>
<div></div>
<div className={styles.header__user}>
{currentUserDoc.email}
<button onClick={logout}>Log out</button>
</div>
</header>
);
};
export default Header;
The onAuthStateChanged observer will trigger when the user logs in or logs out. In case the user has logged out, the user object will be null and hence you will get an error "TypeError: Cannot read property 'uid' of null". You should check if the user is still logged in inside of the auth observer.
const unsubscribe = auth.onAuthStateChanged(async (user) => {
// Check if user is present (logged in) or absent (logged out)
if (!user) {
// user has logged out
console.log("No User")
} else {
// Add the required documents
setCurrentUser(user);
const userDoc = db.collection("users").doc(user.uid);
const doc = await userDoc.get()
if (doc.exists) {
setCurrentUserDoc(doc.data());
} else {
await userDoc.set({
email: user.email,
first_name: "",
last_name: "",
country: "",
organization: "",
});
}
}
})

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