I Created a node.js server and i can write user info to MongoDb and i can create JWT in postman. so i want to use this on react project.
i created react router with private route which it's checking if there is an any user info in the local storage. example , (i did not create a axios post for login api. i just want to write user info with hardcode for see the code is working)
import { useAuth } from "../Context/AuthContext";
import { Navigate,useLocation } from "react-router-dom";
export default function PrivateRoutes({children}){
const user = JSON.parse(localStorage.getItem('user'))
const location = useLocation();
if(!user){
return <Navigate to="/login" state={{return_url:location.pathname}} />
}
return children;
}
authcontext
So , when i'm in a login page , i created a button and if i click this button i want to access to my AuthProvider and set user info to the LocalStorage in AuthProvider.
Login page,
LoginPage.js
import { useAuth } from "../../../Context/AuthContext";
import { useNavigate,useLocation } from "react-router-dom";
export default function LoginPage(){
const navigate = useNavigate();
const location = useLocation();
const { setUser } = useAuth();
const loginHandle = () => {
setUser({
id : 1,
username : 'umitcamurcuk'
})
navigate(location?.state?.return_url || '/');
}
const logoutHandle = () => {
setUser(false);
navigate('/');
}
return (
<div>
<button onClick={loginHandle}>Sign In</button>
<button onClick={logoutHandle}>Cikis yap</button>
</div>
)
}
and my AuthContext page,
AuthContext.js
import { createContext, useState , useContext, useEffect } from "react";
const Context = createContext()
export const AuthProvider = ({ children }) => {
const [user , setUser] = useState(JSON.parse(localStorage.getItem('user')) || false);
const data = [ user, setUser ]
useEffect(() => {
localStorage.setItem('user', JSON.stringify(user))
},[user])
return(
<Context.Provider value={data}>
{children}
</Context.Provider>
)
}
export const useAuth = () => useContext(Context);
But when i click login button , this error show up
error
LoginPage.js:12 Uncaught TypeError: setUser is not a function
and my indexJS
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import {BrowserRouter} from 'react-router-dom';
import { AuthProvider } from './Context/AuthContext.js';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<AuthProvider>
<App />
</AuthProvider>
</BrowserRouter>
);
is anyone for help me ?
Thanks
You defined your value as array and not an object
const [user, setUser] = useAuth();
or
const data = { user, setUser }
Related
I have created a useAuth hook in order to allow the user access to the private route once authorised the authorising cookie is stored properly but the useAuth hook seems to be returning null. I also tried printing the value of setUser but it doesn't seem to print after the line setUser(res.data.currentUser) but the one before is printed I cannot figure out why.
The cookies are stored and set properly it just seems to be the auth hook that return setUser as null even though the line console.log("RES>DATA = ", res.data.currentUser); logs the correct details of the cookie but when I try to manually change the url to the private route after the cookie is stored it returns a null for the setUser returned from the auth hook and so access to private route isn't granted. This is done using react for frontend with node js and express for the backend. If any more context is necessary please let me know.
useAuth hook:
import { useState, useContext, useEffect } from "react";
import { UserContext } from "./UserContext";
import axios from "axios";
export default function useAuth() {
const { setUser } = useContext(UserContext);
const [error, setError] = useState(null);
useEffect(() => {
console.log("USE effect");
setUserContext();
});
//set user in context and push them home
const setUserContext = async () => {
console.log("In here");
return await axios
.get("auth/checkAuth")
.then((res) => {
console.log("RES>DATA = ", res.data.currentUser); // PRINTS THE CORRECT COOKIE VALUES
setUser(res.data.currentUser); // THIS SEEMS TO BE NULL?????
console.log("SET USER + ", setUser); // THIS DOES NOT PRINT
})
.catch((err) => {
setError(err);
});
};
return {
setUser,
};
}
Private route:
import React, { useContext } from 'react';
import {Route, Redirect, Navigate} from 'react-router-dom';
import { UserContext } from '../hooks/UserContext';
import useAuth from '../hooks/useAuth';
export default function PrivateRoute({children}) {
const auth = useAuth();
console.log("aUTH in priv = ", auth);
return auth ? children : <Navigate to="/"/>
}
App.js:
import "./App.css";
import { Redirect, Route, Routes, Switch } from "react-router-dom";
import useFetch from "./useFetch";
import { useEffect, useState, useRef } from "react";
import axios from "axios";
import Activities from "./components/Activities";
import HomePage from "./components/Home";
import Map from "./components/Map";
import checkUser from "./hooks/checkUser";
import { UserContext } from "./hooks/UserContext";
import useFindUser from "./hooks/checkUser";
import PrivateRoute from "./components/PrivateRoute";
function App() {
const [auth, setAuth] = useState(false);
const [activities, setActivities] = useState([]);
const notInitialRender = useRef(false);
const { user, setUser, isLoading } = useFindUser(); // works as expected
return (
<div className="App">
<UserContext.Provider value={{ user, setUser, isLoading }}>
<Routes>
<Route path="/" element={<HomePage />}></Route>
<Route path="/Activities" element={<Activities />} />
<Route
path="/Private"
element={
<PrivateRoute>
<Map />
</PrivateRoute>
}
/>
</Routes>
</UserContext.Provider>
</div>
);
}
export default App;
auth/checkAuth express route:
export const checkAuth = (req, res) => {
let currentUser;
console.log("res.cookies = ", req);
if (req.cookies.currentUser) {
// res.send(200);
currentUser = req.cookies.currentUser; // set to user object in cookies
console.log("current user = ", currentUser);
// return 200 status code
} else {
currentUser = null;
}
//res.json(currentUser);
res.status(200).send({ currentUser });
};
Map component which is private route component:
import react from 'react';
function Map() {
<div>
<p style={{color:'red'}}>You have access to the private route</p>
</div>
}
export default Map;
UserContext file:
import { createContext } from "react";
export const UserContext = createContext("null");
I tried logging values and using useEffect to call the function when the useAuth hook is called but couldn't figure it out
I'm working on a web application with next.js and I have a separated API where I generate the JWT token.
I have a login form that I use to send the user info and recover the JWT. Everything is working fine, but when I persist the data and I try to redirect the user to the dashboard which is supposed to be a protected route, it stays protected even though the user is authenticated. I don't know what am I doing wrong..
Here is the context file :
import React, {createContext, useEffect, useState} from 'react'
import { useRouter } from "next/router";
import axios from 'axios'
export const Context = createContext(null)
const devURL = "http://localhost:4444/api/v1/"
export const ContextProvider = ({children}) => {
const router = useRouter()
const [user, setUser] = useState()
const [userToken, setUserToken] = useState()
const [loading, setLoading] = useState(false)
const [successMessage, setSuccessMessage] = useState("")
const [errorMessage, setErrorMessage] = useState("")
const Login = (em,pass) => {
setLoading(true)
axios.post(devURL+"authentication/login", {
email : em,
password : pass
})
.then((res)=>{
setSuccessMessage(res.data.message)
setErrorMessage(null)
setUser(res.data.user)
setUserToken(res.data.token)
localStorage.setItem('userToken', res.data.token)
localStorage.setItem('user', res.data.user)
setLoading(false)
})
.catch((err)=>{
setErrorMessage(err.response.data.message)
setSuccessMessage(null)
setLoading(false)
})
}
const Logout = () => {
setUserToken()
setUser()
localStorage.removeItem('userToken')
localStorage.removeItem('user')
router.push('/authentication/admin')
}
const isUserAuthenticated = () => {
return !!userToken
}
useEffect(()=>{
let token = localStorage.getItem('userToken')
if(token){
setUserToken(token)
}
},[userToken])
return (
<Context.Provider value={{
Login,
user,
loading,
userToken,
setUserToken,
Logout,
successMessage,
setSuccessMessage,
setErrorMessage,
isUserAuthenticated,
errorMessage}}>
{children}
</Context.Provider>
)
}
and here is the dashboard page :
import React, {useContext, useEffect} from 'react'
import DashboardIndexView from '../../views/dashboard'
import { useRouter } from "next/router";
import { Context } from '../../context/context';
export default function DashboardIndex() {
const router = useRouter();
const {isUserAuthenticated} = useContext(Context);
console.log(isUserAuthenticated())
useEffect(()=>{
if(isUserAuthenticated()) {
router.push("/dashboard")
} else {
router.push("/authentication/admin")
}
}, [])
return (
<>
<DashboardIndexView />
</>
)
}
I believe that the useEffect in the dashboard page is always going to be redirecting the user to the authentication form, because I debugged what happens (picture) and apparently the isUserAuthenticated function runs twice, once it returns false, and then true, and the first return 'false' redirects the user to the authentication form.
How can I fix this ?
Picture of the console :
I am learning React Redux. I am having a very difficult time learning it. Please excuse how bad my code is because I only started learning a few days ago I want to have a store for the logged in user. It will contain their username, email, etc. Currently I am not using sessions/cookies nor even a database for the users. I am simply trying to learn Redux.
I need help with a few things. The state can contain many objects. I just want one object for the user. And because I am currently having trouble with that, how do I display the username without having to .map() because the state is an array?
Here is my current code for the actions/reducers.
import { combineReducers } from "redux";
const defaultUser = [];
// Actions
const LOGIN_USER = "LOGIN_USER";
export function loginUser(user) {
return {
type: LOGIN_USER,
user,
};
}
// Reducers
function user(state = defaultUser, action) {
switch (action.type) {
case LOGIN_USER:
return [
...state,
{
username: action.user,
},
];
default:
return state;
}
}
const usersStore = combineReducers({ user });
export default usersStore;
Here is the App.js file where I want a user to type a username in the input box, then print out their username.
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import "./App.css";
import { Sidebar } from "./components/Sidebar/Sidebar";
import { Content } from "./components/Content/Content";
import { loginUser } from "./store";
const App = () => {
const [user, setUser] = useState("");
const selectedUser = useSelector((state) => state.user);
const dispatch = useDispatch();
const handleSubmit = (event) => {
event.preventDefault();
dispatch(loginUser(user));
setUser("");
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setUser(e.target.value)}
value={user}
/>
<br />
<input type="submit" />
</form>
<br />
<br />
{selectedUser.map((selectUser) => (
<li key={selectUser.username}>
<h3>{selectUser.username}</h3>
</li>
))}
<Content />
</div>
);
};
I figured it out on my own. With my new setup, all I have to do is type {user.username} to get the username of the logged in user. I am fetching it with useSelector().
It basically sets the username as a state temporarily, then sends it over to the store via dispatch, then empties out the state afterwards.
I had to change the initialstate to an object with "profile" inside it (instead of using an array)
I then had to map the state to props of the component I want it to display in, using a function, and then use connect() when exporting it.
Here's my new component code, the bottom few lines are the most important:
import React, { useState } from "react";
import { connect, useDispatch, useSelector } from "react-redux";
import "./App.css";
import { Sidebar } from "./components/Sidebar/Sidebar";
import { Content } from "./components/Content/Content";
import { loginUser } from "./store";
const App = (props) => {
const [stateUser, stateSetUser] = useState("");
const user = useSelector((state) => state.user.profile);
const dispatch = useDispatch();
const handleSubmit = (event) => {
event.preventDefault();
dispatch(loginUser(stateUser));
stateSetUser("");
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => stateSetUser(e.target.value)}
/>
<input type="submit"></input>
</form>
{user.username}
<Content />
</div>
);
};
const mapStateToProps = (state) => {
return {
profile: state.user.profile,
};
};
export default connect(mapStateToProps)(App);
My reducer/actions file: (changes in initialstate are most important)
import { combineReducers } from "redux";
const initialState = {
profile: {
username: "",
email: "",
profileImage: "",
},
};
// Actions
const LOGIN_USER = "LOGIN_USER";
export function loginUser(user) {
return {
type: LOGIN_USER,
user,
};
}
// Reducers
function user(state = initialState, action) {
switch (action.type) {
case LOGIN_USER:
return {
...state,
profile: { username: action.user },
};
default:
return state;
}
}
const usersStore = combineReducers({ user });
export default usersStore;
I tried to create an authentication website with Firebase using email and password. I can't even load the Login page.
Here's Auth.js
import React, { useState, useEffect} from "react";
import { auth } from './config'
import { onAuthStateChanged } from "firebase/auth";
export const AuthContext = React.createContext();
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
useEffect(() => {
onAuthStateChanged(auth, (user) => {
setCurrentUser(user);
})
}, [])
return (
<AuthContext.Provider value={{currentUser}}>
{children}
</AuthContext.Provider>
)
}
And this is Login.js
import React, {useContext ,useState } from "react";
import Form from "react-bootstrap/Form";
import Button from "react-bootstrap/Button";
import "./Login.css";
import { auth } from './config'
import { signInWithEmailAndPassword } from "firebase/auth";
import { AuthContext } from "./Auth";
import { useHistory } from "react-router-dom";
const Login = () => {
let history = useHistory();
const handleSubmit = (event) => {
event.preventDefault();
const { email, password } = event.target.elements;
signInWithEmailAndPassword(auth, email.value, password.value)
.then((userCredential) => {
const user = userCredential.user;
console.log(user.uid);
})
.catch((error) => {
console.log(error.massage);
});
}
const currentUser = useContext(AuthContext);
if(currentUser) {
return history.push('/dashboard');
}
return (
<div className="Login">
<h1>Login</h1>
<Form onSubmit={handleSubmit}>
//Login Form
</Form>
</div>
);
}
export default Login
And DashBoard.js
import React, {useContext} from 'react'
import { AuthContext } from './Auth'
import { auth } from './config'
import { signOut } from 'firebase/auth'
import { useHistory } from "react-router-dom";
const DashBoard = () => {
const currentUser = useContext(AuthContext);
let history = useHistory();
if(!currentUser) {
return history.push('/login');
}
const signOutFunc = () => {
signOut(auth)
}
return (
<div>
<div className='container mt-5'>
<h1>Welcome</h1>
<h2>If you see this you are logged in.</h2>
<button className='btn btn-danger' onClick={signOutFunc}>Sign Out</button>
</div>
</div>
)
}
export default DashBoard;
Lastly App.js
import { BrowserRouter as Router, Route, Switch} from 'react-router-dom'
import Login from './Login'
import DashBoard from './DashBoard';
import { AuthProvider } from './Auth'
function App() {
return (
<AuthProvider>
<Router>
<Switch>
<Route exact path="/login" component={Login} />
<Route exact path="/dashboard" component={Dashboard} />
</Switch>
</Router>
</AuthProvider>
);
}
export default App;
When I open /login, it would send me to /dasgboard immediately. If I typed /login again it gives me this error
Error: Login(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
I can't figure it out what's wrong with it. Please help me.
Thank you
You have multiple places in your code where you return history.push('/dashboard'); or another path. You should return there a null:
if(!currentUser) {
history.push('/login');
return null
}
I am trying to make a protected route with Reatjs, nodejs and JWT. The problem is that my component renders before my API checked the client token. This is the code I am trying :
import React, {useState, useEffect} from 'react';
import { Route, Redirect } from 'react-router-dom';
import AuthAPI from './../utils/AuthAPI';
const ProtectedRoute = ({children, ...rest}) => {
const [isAuth, setIsAuth] = useState(false);
const fetchData = async () => {
await AuthAPI.isAuth((res)=>{ //API call
setIsAuth(res);
});
}
useEffect(()=>{
fetchData();
},[]);
return (
<Route {...rest}
render={(props)=>{
return(
isAuth ? children : <Redirect to='/' />
);
}}
/>
);
};
And this is the API call :
static isAuth(callback){ //static method from the class 'AuthAPI' imported above
const url = 'http://localhost:5000/api/Auth/checking';
const options = {
method: 'GET',
url: url,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8',
},
data: {}
}
return axios(options)
.then((response)=>{
callback(true);
}).catch((err)=>{
callback(false);
});
}
When I load the page, it directly redirects since the state isAuth is set to false by default.
I already used this model of code to display a list of things gotten from an API and it worked fine. I assume it is not the best way to do that but most of the examples I have found are not using an actual API but just fake auth without using promises.
EDIT 1.2 :
I've tried this code, from Udendu Abasili :
import React, {useState, useEffect} from 'react';
import { Route, Redirect } from 'react-router-dom';
import AuthAPI from './../utils/AuthAPI';
const ProtectedRoute = ({children, ...rest}) => {
const [isAuth, setIsAuth] = useState(false);
const [isLoaded, setIsLoaded] = useState(false)
useEffect(()=>{
let mounted = true;
AuthAPI.isAuth().then(()=>{
if (mounted) {
console.log("Worked"); //display Worked
setIsLoaded(true); // This line 1
setIsAuth(true); // This line 2
}
}).catch(()=>{
if (mounted) {
console.log("Failed");
setIsLoaded(true);
setIsAuth(false);
}
});
return () => {
mounted = false;
}
},[]);
return (
!isLoaded ?
<h5>Loading</h5> : (
<Route {...rest}
render={(props)=>{
console.log("--->",isAuth,",",isLoaded); // displays false, true
return(
isAuth ? children : <Redirect to='/' />
);
}}
/>
)
);
};
export default ProtectedRoute;
I have found a weird bug. If I swap the lines commented as 'line 1' and 'line 2', it works otherwise it doesn't.
The way react js lifecycle works, the return component gets called before useEffect(which the hook equivalent of componentDidMount on the first mount). So you need to create a form of loader component ( replace the <Text>Loading</Text> with an actual CSS loader ) that waits for your isAuth function to finish.
const ProtectedRoute = ({children, ...rest}) => {
const [isAuth, setIsAuth] = useState(false);
const [loaded, setLoaded] = useState(false);
const fetchData = async () => {
//you need to add try catch here
await AuthAPI.isAuth((res)=>{ //API call
setIsAuth(res);
setLoaded(true)
});
}
useEffect(()=>{
fetchData();
},[]);
return (
loaded ?
<Text>Loading</Text> : (
<Route {...rest}
render={(props)=>{
return(
isAuth ? children : <Redirect to='/' />
);
}}
)
/>
);
};
As you rightfully said, this is not the best way to do it. I won't recommend calling a function to check authentication in the protected route component. Typically, I just pass an isAuthenticated paramter to ProctectedRoute component which gets updated with help of Redux. You should look it up