I have been trying to create a checkAuth function all day, backend is creating a token and saving it in mongodb, when someone logs in the token gets overwritten to a new token, so only one session can be logged in at a time. However, my checkAuth function always returns true.
CheckAuth.js
import React from "react"
import axios from "axios"
const checkAuth = async () => {
const userInfo = localStorage.getItem("userInfo");
const info = JSON.parse(userInfo);
const email = info.email;
const origintoken = info.token;
console.log("origin");
try {
const config = {
headers: {
"Content-type": "application/json"
}
}
const { data } = await axios.post (
"/api/users/token",
{email},
config
);
const token = data.token;
console.log("origin");
if (origintoken === token) {
console.log("origin");
return true;
}
else {
console.log("else");
return false;
}
} catch (error) {
console.log("error");
return false;
}
}
export default checkAuth
LandingPage.js
import React from "react"
import AuthCheck from "./CheckAuth.js"
import { useEffect } from "react"
import {Redirect} from "react-router-dom"
import { useState} from "react"
import checkAuth from "./CheckAuth.js"
export default function LandingPage() {
const [redirect, setRedirect] = useState(false);
useEffect(() => {
if(!checkAuth()) {
setRedirect(true);
console.log("false");
}}, [])
if (redirect) {
<Redirect to="/"/>
}
return (
<div>
<h1>whatsup</h1>
</div>
)
}
Serverside:
const checkToken = asyncHandler(async (req, res) => {
const email = req.body;
const user = await User.findOne(email)
if (user) {
res.json({
token: user.token,
});
} else {
res.status(400)
throw new Error("Niet ingelogd.")
}
});
As checkAuth is an async function, it always returns a promise which resolves to the value that you return in try block, or rejects with the value that you return in the catch block.
resolves to either true or false:
if (origintoken === token) {
console.log("origin");
return true;
}
else {
console.log("else");
return false;
}
rejects with false:
catch (error) {
console.log("error");
return false;
}
So you can't test its return value like a normal function. instead:
useEffect(async () => {
let authResult=await checkAuth();
if (!authResult) { // first correction
setRedirect(true);
}
}, [])
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function#description
The component should probably look something like this:
import React from "react"
import AuthCheck from "./CheckAuth.js"
import { useEffect } from "react"
import { Redirect } from "react-router-dom"
import { useState } from "react"
import checkAuth from "./CheckAuth.js"
export default function LandingPage() {
const [redirect, setRedirect] = useState(false);
useEffect(() => {
if (!checkAuth()) { // first correction, call the function
setRedirect(true);
}
}, [])
if (redirect) {
return <Redirect to="/"/> // second correction, missing return
}
return (
<div>
<h1>whatsup</h1>
</div>
)
}
You can also do this in the useEffect:
useEffect(() => {
setRedirect(!checkAuth())
}, [])
Update:
As suggested in an answer by Erfan Naghashlou, checkAuth is async function so that useEffect should be modified as:
useEffect(() => {
const x = async () => {
const checkAuthResult = await checkAuth()
setRedirect(!checkAuthResult)
}
x()
}, [])
Look at how to handle async await in useEffect
Related
I have a created useAxiosPrivate hook and I want to use it in a service function I have created using axios which I used to export diffrent methods. But since its not a functional or class component I get an error react hooks must be called in a react function component or a custom react hook function
useAxiosPrivate.tsx
import { axiosPrivate } from '../api/axios'
import { useEffect } from 'react'
import useRefreshToken from './useRefreshToken'
import useAuth from './useAuth'
const useAxiosPrivate = () => {
const refresh = useRefreshToken()
const { auth }: any = useAuth()
useEffect(() => {
const requestIntercept = axiosPrivate.interceptors.request.use(
(config) => {
config.headers = config.headers ?? {}
if (!config.headers['Authorization']) {
config.headers['Authorization'] = `Bearer ${auth?.accessToken}`
}
return config
},
(error) => Promise.reject(error),
)
const responseIntercept = axiosPrivate.interceptors.response.use(
(response) => response,
async (error) => {
const prevRequest = error?.config
if (
(error?.response?.status === 403 || error?.response?.status === 401) &&
!prevRequest?.sent
) {
prevRequest.sent = true
const newAccessToken = await refresh()
prevRequest.headers['Authorization'] = `Bearer ${newAccessToken}`
return axiosPrivate(prevRequest)
}
return Promise.reject(error)
},
)
return () => {
axiosPrivate.interceptors.request.eject(requestIntercept)
axiosPrivate.interceptors.response.eject(responseIntercept)
}
}, [auth, refresh])
return axiosPrivate
}
export default useAxiosPrivate
I want to use this in auth.service.tsx
import useAxiosPrivate from "../hooks/useAxiosPrivate"
const axiosPrivate = useAxiosPrivate(); <-- 'I want to use this in this'
export const SharedService {
UpdateProfile: async (firstName:string, lastName:string) => {
const response = await axiosPrivate.put('/user/me',{
firstName,
lastName,
})
}
I get error that hooks should be used at top level or inside functional component or class how do I fix it ?
Your service must be a hook as well so it can use other hooks
import useAxiosPrivate from "../hooks/useAxiosPrivate";
export const useSharedService = () => {
const axiosPrivate = useAxiosPrivate();
return {
UpdateProfile: async (firstName: string, lastName: string) => {
const response = await axiosPrivate.put("/user/me", {
firstName,
lastName,
});
},
};
};
I am implementing a simple logout functionality if my local storage doesn't have a particular key-value pair and if the value is empty or if the 'token' inside the value is expired.
My current Code: TokenExpired.js
import { isExpired } from "react-jwt";
import { useNavigate } from "react-router-dom";
export const VerifyAccessToken = () => {
const navigate = useNavigate()
const Data = localStorage.getItem('Admin Credentials')
const existanceOfData = Data !== null
if (existanceOfData) {
if (Data && Data !== 'undefined') {
const tokenExpired = isExpired(JSON.parse(Data).accessToken);
if (tokenExpired) {
localStorage.removeItem("Admin Credentials");
navigate('/')
}
} else {
localStorage.removeItem("Admin Credentials");
navigate('/')
}
} else {
navigate('/')
}
}
I am using this in My Dashboard Page : Dashboard/Dashboard/js
import "./Dashboard.scss";
import { adminAuth } from "../../helpers/AdminInformation";
import { VerifyAccessToken } from "../../helpers/TokenExpired";
// components ---------------------------------
certain components
import { useEffect, useState } from "react";
const Dashboard = () => {
const [dashboard, setDashboard] = useState({ received: 0, expected: 0 })
const token = adminAuth.accessToken;
useEffect(() => {
fetch(baseURL + 'api/dashboard/', {
headers: {
token: `Bearer ${token}`
}
}).then(res => res.json()).then(json => setDashboard(json));
}, [token])
VerifyAccessToken();
return (
<div className="dashboard">
content
</div>
);
}
export default Dashboard;
Whenever I try to delete that key value after logging in, it shows error:
I Found the Answer to my Question:
I figured I need to make my Routes Strong so that there are no warning about my routes in the console
I Created Layout Component for my Dashboard Page & Update Token Expired Code with useEffect(), It worked...
My Updated Code: TokenExpired.js
import { isExpired } from "react-jwt";
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
export const VerifyAccessToken = () => {
const navigate = useNavigate()
const Data = localStorage.getItem('Admin Credentials')
const existanceOfData = Data !== null
useEffect(() => {
if (existanceOfData) {
if (Data && Data !== 'undefined') {
const tokenExpired = isExpired(JSON.parse(Data).accessToken);
if (tokenExpired) {
navigate('/')
}
} else {
navigate('/')
}
} else {
navigate('/')
}
}, [Data, existanceOfData, navigate]);
}
I want to show this advice from this API I have fetched all the data but when I try to show it, it throws the error, and also I tried to map the data but nothing works it shows errors
import React, { useState, useEffect } from 'react';
export default function App() {
const [advices, setAdvices] = useState([]);
console.log(advices); // you should have the fetch data here
async function fetchData() {
try {
const response = await fetch('https://api.adviceslip.com/advice');
const data = await response.json();
setAdvices(data.slip);
} catch (error) {
console.error(error);
}
}
useEffect(() => {
fetchData();
}, []);
return <></>;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
import React, {
useState,
useEffect
} from 'react';
export default function App() {
const [advices, setAdvices] = useState([]);
//advices will always be null here as initiated with empty array
console.log(advices); // you should have the fetch data here
async function fetchData() {
try {
const response = await fetch("http://jsonplaceholder.typicode.com/Posts");
const data = await response.json();
setAdvices(data);
} catch (error) {
console.error(error);
}
}
useEffect(() => {
fetchData();
}, []);
useEffect(() => {
console.log(advices);
}, [advices]);
return ( < > Test < />);
}
the initial value of advice should be an object and there was not another serious problem
codesand box link
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [advices, setAdvices] = useState([]);
console.log(advices); // you should have the fetch data here
async function fetchData() {
try {
const response = await fetch("https://api.adviceslip.com/advice");
const data = await response.json();
setAdvices(data.slip);
} catch (error) {
console.error(error);
}
}
useEffect(() => {
fetchData();
}, []);
return (
<div className="App">
<h1>{advices.advice}</h1>
</div>
);
}
In order to receive a value from the server and pass it to the subcomponent, useLayoutEffect is used. By the way, useLayoutEffect is executed after the screen is all drawn.
Passing values is difficult because of the order of execution.
Q1. I want to run useLayoutEffect before rendering, or know another way.
Is there any way you can recommend?
Q2. At the bottom of the ApiServiece.js code, there is json as the return value for the response. But when I output json, promise[{pending}] is output. What is wrong?
I'm studying on my own, so I'm desperate for help. Any comments are welcome, so please help.🥲
Code Structure
// AdminContainer.js
import React, { useLayoutEffect, useEffect, useState, useRef } from "react";
import { useResolvedPath } from "react-router";
import { getAllUserList } from "../service/ApiService";
import DisplayUser from "./DisplayUser";
const AdminContainer = () => {
const [allUsers, setUsers] = useState([]);
useLayoutEffect (() => {
setUsers(getAllUserList);
// console.log(getAllUserList()); // Promise {<pending>}
}, []);
return (
<div>
{/* {JSON.stringify(allUserList[0])} */}
{/* {allUserList.map((user, i) => {
return <DisplayUser inputUser={user} key={i} />
})} */}
<DisplayUser inputUser={allUsers} />
</div>
)
}
export default AdminContainer;
// ApiServiece.js
import { connect } from 'react-redux';
import { login } from '../modules/authentication';
import store from '../modules/store';
import { API_BASE_URL } from "./app-config";
import { resolvePath } from 'react-router';
import { Sync } from '#material-ui/icons';
const ACCESS_TOKEN = "ACCESS_TOKEN";
export function signin(userDTO) {
console.log(store.getState());
return call("/auth/signin", "POST", userDTO).then((response) => {
if (response.token) {
localStorage.setItem(ACCESS_TOKEN, response.token);
localStorage.setItem("SequenceEmail", response.email);
window.location.href = "/";
}
});
}
export function signup(userDTO) {
return call("/auth/signup", "POST", userDTO);
}
export function call(api, method, request) {
let headers = new Headers({
"Content-Type": "application/json",
});
const accessToken = localStorage.getItem("ACCESS_TOKEN");
if (accessToken && accessToken !== null) {
headers.append("Authorization", "Bearer " + accessToken);
}
let options = {
headers: headers,
url: API_BASE_URL + api,
method: method,
};
if (request) {
options.body = JSON.stringify(request);
}
return fetch(options.url, options)
.then((response) =>
response.json().then((json) => {
if (!response.ok) {
return Promise.reject(json);
}
return json;
})
)
.catch((error) => {
console.log(error.status);
if (error.status === 403) {
window.location.href = "/login";
}
return Promise.reject(error);
});
}
export function getAllUserList() {
let result = call("/auth/getAllUerList", "GET");
return result;
}
// DisplayUser.js
const DisplayUser = (inputUser) => {
const userRef = useRef(inputUser);
const [user, setUser] = useState();
useEffect(() => {
setUser(userRef.current.inputUser);
}, [])
console.log('setUser isArray:' + Array.isArray(setUser(userRef.current.inputUser)));
return (
// <div> {user.username} </div>
<div> test </div>
)
}
export default DisplayUser;
CodeSandbox order of execution Link
So first thing first you are assigning to the allUsers variable (which should be a list) a function, which doesn't make sense. You have to first CALL the function (and using the useLayoutEffect you WILL call it before rendering, but the request will resolve asyncronously, so and you will have to populate allUsers only when the request is resolved). One solution would be to show a spinner in the meanwhile for example:
// AdminContainer.js
import React, { useLayoutEffect, useEffect, useState, useRef } from "react";
import { useResolvedPath } from "react-router";
import { getAllUserList } from "../service/ApiService";
import DisplayUser from "./DisplayUser";
const AdminContainer = () => {
const [allUsers, setUsers] = useState([]);
const [showSpinner, setSpinnerIsShowing] = useState(false)
useLayoutEffect (() => {
setSpinnerIsShowing(true)
getAllUserList().
then(users => setUsers(users))
.catch(err => /*Handle error*/)
.finally(()=> setSpinnerIsShowing(false))
}, []);
return (
showSpinner ? <SomeSpinner /> : <div>
{/* {JSON.stringify(allUsers[0])} */}
{/* {allUsers.map((user, i) => {
return <DisplayUser inputUser={user} key={i} />
})} */}
<DisplayUser inputUser={allUsers} />
</div>
)
}
export default AdminContainer;
Then in ApiService.js I would use an async await syntax to make everything more readable.
The difference is that async functions will always return a promise, so you don't need to use the Promise class directly (throw will be equivalent to reject, while return will be the same as resolve).
export async function call(api, method, request) {
let headers = new Headers({
"Content-Type": "application/json",
});
const accessToken = localStorage.getItem("ACCESS_TOKEN");
if (accessToken && accessToken !== null) {
headers.append("Authorization", "Bearer " + accessToken);
}
let options = {
headers: headers,
url: API_BASE_URL + api,
method: method,
};
if (request) {
options.body = JSON.stringify(request);
}
const response = await fetch(options.url, options)
const json = await response.json()
if (!response.ok) {
const error = new CustomError("Request not OK :C");
error.customData = json;
throw error
}
return json;
//console.log(err.status);
//if (err.status === 403) {
// window.location.href = "/login"; <-- Nope: handle this when calling the function
//}
}
Note the Custom Error that you can create to define additional data extending the default javascript Error class:
class CustomError extends Error {
constructor(message, customData = {}){
super(message)
this.customData = customData
}
}
Also the getUserList function is an asyncronous one:
And you can create an async function in two ways:
with async syntax
export async function getAllUserList() {
return await call("/auth/getAllUserList", "GET");
}
with promise sintax:
export function getAllUserList() {
return new Promise((reject,resolve)=>{
call("/auth/getAllUserList", "GET")
.then(res => resolve(res))
.catch(err => reject(err))
})
}
And those two are equivalent.
Let me know in the comments if you need some clarifications :)
Well, I have a component that requires a token in order to fetch data from DB. The user usually gets the token after a successfull login:
const loginAuth = (loginData, history) => async () => {
const res = await api.post(`/auth/login`, loginData);
if (res.statusText === `OK`) {
if (res.data.data) {
history.push(`/auth/validatetwofactorauth/${res.data.data._id}`);
return res.data;
}
setAuthToken(res.data.token);
history.push('/posts');
} else {
console.log('error');
// setError(res.data.error);
}
};
As you guys can see I'm calling a method with the name of sethAuthToken, said function looks like this:
export const setAuthToken = (token) => {
if (token) {
if (typeof window !== 'undefined') {
api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
api.defaults.headers['Authorization'] = `Bearer ${token}`;
localStorage.setItem('xAuthToken', token);
}
} else {
delete api.defaults.headers.common['Authorization'];
delete api.defaults.headers['Authorization'];
localStorage.removeItem('xAuthToken');
deleteCookie('xAuthToken', '/');
}
};
After the token is received, the function above passes it to the api.default.headers.common; this is my api:
import axios from 'axios';
import { API_URL } from '../config';
const api = axios.create({
baseURL: `${API_URL}/api/v1`,
headers: {
'Content-Type': `application/json`
}
});
console.log('API', api.defaults);
// So far everything works great since I can actually see the token being
// retrieved in the previous console log()
api.interceptors.response.use(
(res) => res,
(err) => {
return Promise.reject(err);
}
);
export default api;
So far, everything works great!.
Now, the problem comes on this following request and component. This component fetches data from a request that do requires the a Bearer token(which by now should be available)
import { useEffect, useState, useContext } from 'react';
import { withRouter } from 'next/router';
// ACTIONS
import { getTimelineFromServer } from '#/actions/post';
// HELPERS
import NothingFoundAlert from '#/layout/NothingFoundAlert';
import privateRoutes from '#/routing/privateRoutes';
import AuthContext from '#/routing/authContext';
// REACSTRAP
import CardColumns from 'react-bootstrap/CardColumns';
// NESTED COMPONENTS
import Single from './single';
export const getServerSideProps = async (context) => {
const params = `?page=${context.query.page}&limit=${context.query.limit}&sort=${context.query.sort}&status=published`;
const data = (await getTimelineFromServer(params)()) || []; // Get videos
if (!data) {
return { notFound: true };
}
return {
props: {
params: params,
serverPosts: data.data,
}
};
};
const Timeline = ({
params,
serverPosts,
router
}) => {
const [posts, setPosts] = useState([]);
useEffect(() => {
setPosts(serverPosts);
}, [params]);
const { auth } = useContext(AuthContext);
return posts?.length > 0 ? (
<CardColumns>
{posts.map((post, index) => (
<Single
key={post._id}
post={post}
objects={posts}
auth={auth}
/>
))}
</CardColumns>
) : (
<NothingFoundAlert />
)
};
export default withRouter(privateRoutes(Timeline));
The function that requires it, is the getTimelineFromserver(params)() || []; this is what it looks like:
export const getTimelineFromServer = (params) => async (dispatch) => {
console.log(api.defaults.headers.common);
try {
const res = await api.get(`/posts/timeline${params}`);
console.log('Get timeline', api.defaults.headers.common);
return res.data;
} catch (err) {
return { msg: err?.response?.statusText, status: err?.response?.status };
}
};
Now, I'm assuming the error is in the api.defaults.headers.common because when I console.log-it, it only returns { Accept: 'application/json, text/plain, */*' }.
In summary: the token gets deleted for some reason?
Thank you and I know this post is long but I would not be asking if I had not tried anything before; now, I'm out of ideas.
NOTE: I have a privateRoutes HOC that I use with the component above but I do not think that has anything to do with the fetching since the only thing it does is verify if X token is found, otherwise, redirect user to /aut/login