I am working on a login page and trying to store my data in context. But i get response from the database as undefined.
I think i am doing something wrong. Help please
Here's my code snippet
"AUTH CONTEXT" (The auth Context file returns undefined as response)
import { signIn as signInApi } from '../apis'
const AuthContext = createContext()
export const AuthProvider = ({ children }) => {
const [token, setToken] = useState(localStorage.getItem('token'))
const [user, setUser] = useState(localStorage.getItem('user'))
const [loading, setLoading] = useState(false)
const signIn = async (email, password, callback) => {
setLoading(true)
const res = await signInApi(email, password)
console.log(res)
......
const value = {
token,
loading,
signIn,
signOut,
}
}
export default AuthContext
APIS.JS (The API.js file below returns response data from the database)
import axios from 'axios'
export const signIn = async (email, password) => {
try {
const res = await axios.post(
`${process.env.REACT_APP_API}/auth/login`,
{
email,
password,
},
{
headers: {
'Content-Type': 'application/json',
},
}
)
} catch (error) {
console.log(error)
}
}
LOGIN FILE
const auth = useContext(AuthContext)
const handleLogin = (e) => {
e.preventDefault()
auth.signIn(email, password, () => history.replace('/admin'))
}
you didnot return response from your signIn function in API.js file
Related
I have used the context in other places, such as login, database functions, and more. However, when I try to run functions or variables inside my context in places such as custom api's or getServerSideProps, it returns the following error, TypeError: Cannot read properties of null (reading 'useContext'). I am attaching my auth context, my initialization of the context, and the getServerSideProps function that is returning an error
_app.js
import RootLayout from '../components/Layout'
import { AuthProvider } from '../configs/auth-context'
import '../styles/globals.css'
export default function App({ Component, pageProps }) {
return (
<AuthProvider >
<RootLayout>
<Component {...pageProps} />
</RootLayout>
</AuthProvider>
)}
auth-context
import React, { useContext, useState, useEffect, useRef } from 'react'
import { auth, db, provider } from './firebase-config'
import { GoogleAuthProvider, signInWithEmailAndPassword, createUserWithEmailAndPassword, signOut, onAuthStateChanged, signInWithPopup } from 'firebase/auth'
import { doc, getDoc, setDoc } from 'firebase/firestore'
import {useRouter} from 'next/router';
const AuthContext = React.createContext({currentUser: {uid: "TestUid", email:"Testeremail#email.com"}})
export function UseAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const router = useRouter();
const [currentUser, setCurrentUser] = useState({uid: "TestUid", email:"Testeremail#email.com"})
const [loading, setLoading] = useState(true)
async function signup(email, password) {
createUserWithEmailAndPassword(auth, email, password)
.then(async (result) => {
const user = result.user;
await userToDb(user);
router.push('/portfolio');
return user;
}).catch((error) => {
console.error(error);
})
return
}
async function login(email, password) {
return signInWithEmailAndPassword(auth, email, password)
.then(async (result) => {
const user = result.user;
await userToDb(user);
router.push('/portfolio');
return user;
}).catch((error) => {
console.error(error)
})
}
function logout() {
router.push('/')
return signOut(auth)
}
async function googleSignIn() {
const provider = new GoogleAuthProvider();
signInWithPopup(auth, provider)
.then(async (result) => {
const credential = GoogleAuthProvider.credentialFromResult(result);
const token = credential.accessToken;
// The signed-in user info.
const user = result.user;
await userToDb(user);
router.push('/portfolio');
return user
}).catch((error) => {
console.log(error)
// const errorCode = error.code;
// const errorMessage = error.message;
// The email of the user's account used.
// const email = error.customData.email;
// The AuthCredential type that was used.
// const credential = GoogleAuthProvider.credentialFromError(error);
} )
}
const userToDb = async (user) => {
// await setDoc(doc(db, "users", user.uid), {
// userEmail: user.email,
// userID: user.uid
// }, {merge: false})
let currentRef = doc(db, 'users', user.uid)
let currentUserID = user.uid;
let currentEmail = user.email;
await setDoc(currentRef, {
userEmail: currentEmail,
userID: currentUserID
}, {merge: false})
}
function fixData(docs) {
console.log("this works")
// setDocuments(docs);
let retMap = new Map();
if (currentUser !== null) {
docs?.map(function(doc) {
console.log(doc)
let tic = doc.stockTicker
let data = {
shares: doc.shares,
price: doc.price,
type: doc.type
}
if(!retMap.has(tic)) {
retMap.set(tic, [data]);
console.log(tic + " " + data)
// setMap(new Map(datamap.set(tic, {shares: shares, averagePrice: price})))
}
else {
let x = retMap.get(tic);
x.push(data);
}
})
console.log(retMap)
return retMap;
}
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, async user => {
setCurrentUser(user)
setLoading(false)
})
return unsubscribe
}, [])
const value = {
currentUser,
login,
signup,
logout,
googleSignIn,
fixData
}
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
)
}
getServerSideProps
export async function getServerSideProps() {
let allDocs = []
let avgDocs = []
const {currentUser} = UseAuth()
return {
props: {allDocs, avgDocs}
}
}
I don't know the correct answer, but hooks should be used in components and hooks without exception to ssr.
If logIn can be destructured from
const [logIn] = useLogInMutation();
and be used with
const handleLogin = async () => {
const username = 'username';
const password = 'password';
await logIn({
username,
password,
});
};
how do I perform a query similar to something like this
const handleGetUser = async () => {
await getUser();
};
from this
const { data: user, isSuccess, isError, error } = useGetUserQuery();
Export the useLazyQuery hook (useLazyGetUserQuery in this case)
export const {
useLogInMutation,
useGetUserQuery,
useLazyGetUserQuery,
useLogOutMutation,
} = authApi;
Import and use the hook
const [getUser] = useLazyGetUserQuery();
const handleGetUser = async () => {
try {
const user = await getUser().unwrap();
console.log(user);
} catch (err) {
if (err?.status === 401) {
console.error('Unautheticated.');
}
}
};
Thanks #Kapobajza
So what I'm trying to do is to refresh access token stored in ContextAPI by refresh token stored in a cookie. I use axios response interceptor to check 401 & 403 status codes and then call refresh function from useGetNewAccessToken hook which sends new access token from the server. The problem is, React app always keeps pushing me back to the login page on page refresh instead of calling this function and adding newly acquired access token to the Authorization header. Here is how I set my interceptors:
import axios from "axios";
import useGetNewAccessToken from "./useGetNewAccessToken";
import { AuthContext } from "./useAuth";
import { useContext, useEffect } from "react";
export const instance = axios.create({
baseURL: "http://localhost:8080",
headers: {
"Content-Type": "application/json",
},
withCredentials: true,
});
export const useSetInterceptors = () => {
const { accessToken } = useContext(AuthContext);
const refresh = useGetNewAccessToken();
useEffect(() => {
const requestInterceptor = instance.interceptors.request.use(
(config) => {
if (!config.headers["Authorization"]) {
console.log("No authorization header is present");
config.headers["Authorization"] = `Bearer ${accessToken}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
const responseInterceptor = instance.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
const prevRequest = error?.config;
console.log("Request failed");
if (
error?.response?.status === 401 &&
error?.response?.status === 403 &&
!prevRequest.sent
) {
prevRequest.sent = true;
const accessToken = await refresh();
prevRequest.headers["Authorization"] = `Bearer ${accessToken}`;
return instance(prevRequest);
}
return Promise.reject(error);
}
);
return () => {
instance.interceptors.request.eject(requestInterceptor);
instance.interceptors.request.eject(responseInterceptor);
};
}, [accessToken, refresh]);
return instance;
};
export default useSetInterceptors;
I handle authentication process in a custom useAuth hook:
import React, { useState, createContext, useContext, useEffect } from "react";
import axios from "axios";
import { instance } from "../hooks/useSetInterceptors";
import { Link, useNavigate, useLocation } from "react-router-dom";
export const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [authed, setAuthed] = useState(false);
const [moderator, setModerator] = useState(false);
const [accessToken, setAccessToken] = useState("");
const [refreshToken, setRefreshToken] = useState("");
const [authorities, setAuthorities] = useState([]);
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from?.pathname || "/";
const signIn = async (e, username, password) => {
e.preventDefault();
const result = await getTokens(username, password);
if (result) {
console.log("User has signed in");
}
};
const getTokens = async (username, password) => {
const api = `http://localhost:8080/api/v1/public/signIn?username=${username}&password=${password}`;
const res = await instance.get(api, {
withCredentials: true,
params: {
username: username,
password: password,
},
});
const data = await res.data;
setAccessToken(data["access_token"]);
navigate(from, { replace: true });
};
return (
<AuthContext.Provider
value={{
authed,
setAuthed,
moderator,
setModerator,
getTokens,
getAccessTokenAuthorities,
username,
password,
setUsername,
setPassword,
signIn,
isModerator,
accessToken,
setAccessToken,
refreshToken,
setRefreshToken,
}}
>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
This is the hook to send new access token by refresh token:
import axios from "axios";
import { AuthContext } from "./useAuth";
import { useContext } from "react";
export const useGetNewAccessToken = async (props) => {
const { accessToken, setAccessToken } = useContext(AuthContext);
const api = `http://localhost:8080/api/v1/public/getNewAccessToken`;
const refresh = async () => {
try {
const refreshToken = document.cookie.slice(14);
const res = await axios.get(api, {
withCredentials: true,
headers: {
Authorization: `Bearer ${refreshToken}`,
"Access-Control-Allow-Origin": "localhost:8080",
},
});
const data = await res.data;
setAccessToken(data["access_token"]);
return res.data["access_token"];
} catch (error) {
console.log(error);
}
};
return refresh();
};
export default useGetNewAccessToken;
Finally this is how I use interceptors inside my Inventory component:
import React, { useContext, useEffect, useState } from "react";
import useSetInterceptors from "../hooks/useSetInterceptors";
import { AuthContext } from "../hooks/useAuth";
import { useNavigate, useLocation } from "react-router-dom";
const Inventory = (props) => {
const [inventoryItems, setInventoryItems] = useState([]);
const { moderator, accessToken } = useContext(AuthContext);
const setInterceptors = useSetInterceptors();
const navigate = useNavigate();
const location = useLocation();
useEffect(() => {
const getInventoryItems = async () => {
const api = `http://localhost:8080/api/v1/moderator/inventory`;
try {
const res = await setInterceptors.get(api, {
withCredentials: true,
});
const data = await res.data;
console.log(data);
setInventoryItems(data);
} catch (err) {
console.log("Request failed..." + err);
navigate("/signIn", { state: { from: location }, replace: true });
}
};
getInventoryItems();
}, []);
return (
<div>
<h1>Inventory</h1>
</div>
);
};
Inventory.propTypes = {};
export default Inventory;
Can it have anything to do with the fact that interceptors call themselves multiple times instead of one even though I check that in response interceptor if statement and eject them after usage? Calls are shown in here
I'm trying to change the state value once the data has been fetched. I can see that the JSON has been fetched on the network tab but the state value hasn't been changed. State values are logged before the fetch request, I've added await but it hasn't been resolved yet. Do I've to use useEffect for a fetch request, I've tried to use useEffect but it triggers the request once I import this hook is there a workaround?
import axios from 'axios'
import { useState } from 'react'
export const useSignup = () => {
const [loading, setLoading] = useState(true)
const [status, setStatus] = useState(false)
const [msg, setMsg] = useState('')
const registerUser = async (emailAddress, password) => {
try {
await axios
.post('/signup', {
emailAddress: emailAddress,
password: password,
})
.then((res) => {
setStatus(res?.data.success)
setMsg(res?.data.msg)
})
.catch((err) => {
setStatus(err?.response.data.success)
setMsg(err?.response.data.msg)
})
} catch (err) {
console.error(err)
setStatus(false)
setMsg('Error Occured')
} finally {
console.log(msg, status)
setLoading(false)
}
}
return { loading, status, msg, registerUser }
}
You should trigger your function call via a useEffect hook.
Also, if you are using async/await you shouldn't mix it with a Promise-based approach.
Modify the custom hook to accept the two parameters, add the useEffect call and edit your registerUser function:
export const useSignup = (emailAddress, password) => {
const [loading, setLoading] = useState(true);
const [status, setStatus] = useState(false);
const [msg, setMsg] = useState('');
const registerUser = async (emailAddress, password) => {
try {
const { data } = await axios.post('/signup', { emailAddress, password })
setStatus(data.success)
setMsg(data.msg)
} catch (err) {
console.error(err);
setStatus(false);
setMsg('Error Occured');
}
};
useEffect(() => {
registerUser(emailAddress, password);
}, [])
return { loading, status, msg, registerUser };
};
Then you can call your useSignup hook like this
const { loading, status, msg, registerUser } = useSignup('username', 'password')
I have a react class component with rather lengthy onSubmit function that I have put into another file in order to keep the code a bit tidier.
I tried to convert the class component to a functional one, replacing all of my state and setState functions with useState but now my useState state updaters are returning undefined inside the imported function. Am I able to update state using an imported function with a functional component? The function worked fine when it was imported into a class component and my state updater was setState();
//Imported function in utils.js
export const loginUser = async function (email, password) {
try {
const login = await axios.post('http://localhost:5000/api/v1/auth/login', {
email,
password
});
const options = {
headers: {
Authorization: `Bearer ${login.data.token}`
}
};
const getUser = await axios.get(
'http://localhost:5000/api/v1/auth/me',
options
);
const user = getUser.data.data;
setAuthenticated(true);
setUser(getUser.data.data);
setEmail('');
setPassword('');
localStorage.setItem('user', JSON.stringify(user));
console.log(localStorage.getItem('user'));
} catch (err) {
console.log(err);
}
};
// Functional component with imported function
import React, { useState } from 'react';
import axios from 'axios';
import PropTypes from 'prop-types';
import { Login } from './Login';
const { loginUser } = require('../utils/utils');
export const Splash = () => {
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
const [authenticated, setAuthenticated] = useState(false);
const [msg, setMsg] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const _handleEmail = (e) => {
setEmail(e.target.value);
};
const _handlePass = (e) => {
setPassword(e.target.value);
};
const _handleSubmit = async (e) => {
e.preventDefault();
loginUser(email, password);
if (user) {
console.log(user);
this.props.onHandleUser(user);
}
};
return (
<div className='splashStyle'>
{!authenticated && (
<Login
handleEmail={_handleEmail}
handlePass={_handlePass}
handleSubmit={_handleSubmit}
isAuthenticated={authenticated}
/>
)}
</div>
);
};d
EDIT: My issue that setAuthenticated, setUser, setEmail, and setPassword are coming undefined in utils.js
Thanks!
One way of achieving that would be passing all the set methods as a paramters to loginUser function.
But a better way of doing this will be like:
create two separate files
1 for login api call like :
login.js
function login(email, password){
const login = await axios.post('http://localhost:5000/api/v1/auth/login', {
email,
password
});
return login.data;
}
another for getting data
getProfile.js
function getProfile(token){
const options = {
headers: {
Authorization: `Bearer ${token}`
}
};
const getUser = await axios.get(
'http://localhost:5000/api/v1/auth/me',
options
);
return getUser.data;
}
Now do you setting state stuff in actuall component submit call function like
const _handleSubmit = async (e) => {
e.preventDefault();
const token = await login(email, password);
const user = await getProfile(token);
if (user) {
console.log(user);
props.onHandleUser(user);
setAuthenticated(true);
setUser(getUser.data.data);
setEmail('');
setPassword('');
localStorage.setItem('user', JSON.stringify(user));
console.log(localStorage.getItem('user'));
}
};
You need to pass the setAuthenticated function to the loginUser function before calling it in that.
return an onSubmiHandler function from your login user hook.
const doLogin = (email , password) => {
/// your code here
}
return {doLogin}
then use the doLogin function inside your main component
//inside functional component
const {doLogin} = loginUser();
onSubmit = () => doLogin(email, password)
for more you can see how to use custom hooks from here
https://reactjs.org/docs/hooks-custom.html
To start loginUser can't know about the setState you insert there try to pass it as arguments and it will fix it 😁
another problem I see is that you use the this keyword and in the functional component you use the just props.
and just for you to know don't pass null as the initial value pass an empty string, number, etc..
Update
this is how you also pass a setState as argument
loginUser((e)=>setEmail(e))