I am making a site whereby after the user signs in, the user is meant to be redirected to the home page. The homepage and all the other pages of the site are only accessible by signed in users but even after a user signs in(firebase auth), the rest of the site(protected routes) is still not accessible and the only page accessible is the login page. The technologies I am using are react, react router dom and firebase and this is how my code looks like, starting with the App.js
import Home from "./pages/home/Home";
import Login from "./pages/login/Login";
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import List from "./pages/list/List";
import User from "./pages/user/User";
import AddNew from "./pages/addnew/AddNew";
import { useContext } from "react";
import { AuthContext } from "./context/AuthContext";
function App() {
const {currentUser} = useContext(AuthContext);
const RequireAuth = ({ children }) => {
return currentUser ? children : <Navigate to="/login" />;
};
return (
<div className="App">
<BrowserRouter>
<Routes>
<Route path="/login" exact element={<Login />} />
<Route path="/" exact element={ <RequireAuth> <Home /> </RequireAuth> } />
<Route path="/users" exact element={<RequireAuth><List /></RequireAuth>} />
<Route path="/users/:id" exact element={<RequireAuth><User /></RequireAuth>} />
<Route path="/add" exact element={<RequireAuth><AddNew /></RequireAuth>} />
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
And then followed by the login.js page
import React,{useState} from 'react'
import { useContext } from 'react';
import { signInWithEmailAndPassword } from "firebase/auth";
import { auth } from '../../firebase';
import "./login.css";
import {useNavigate} from "react-router-dom";
import { AuthContext } from '../../context/AuthContext';
export default function Login() {
const [error, seterror] = useState(false);
const [email, setemail] = useState("");
const [password, setpassword] = useState("");
const navigate = useNavigate();
const {dispatch} = useContext(AuthContext)
const handleLogin = (e) => {
e.preventDefault();
signInWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
const user = userCredential.user;
dispatch({type: "LOGIN", payload: user});
navigate("/");
})
.catch((error) => {
seterror(true);
console.log(error.message);
});
}
return (
<div className='login'>
<form onSubmit={handleLogin}>
<input className='ok' type="email" placeholder='email' onChange={e => setemail(e.target.value)} />
<input className='ok' type="password" placeholder='password' onChange={e => setpassword(e.target.value)} />
<button className='sb'>Submit</button>
{error && <span className='ks'>Wrong email or password</span>}
</form>
</div>
)
}
And then I have the authreducer.js file that deals with the state
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN": {
return {
currentUser: action.payload,
}
}
case "LOGOUT": {
return {
currentUser: null
}
}
default:
return state;
}
}
export default AuthReducer
And finally the authcontext.js file
import { createContext, useEffect, useReducer } from "react";
import AuthReducer from "./AuthReducer";
const INITIAL_STATE = {
currentUser: JSON.parse(localStorage.getItem("user")) || null,
}
export const AuthContext = createContext(INITIAL_STATE);
export const AuthContextProvider = ({children}) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
useEffect(() => {
localStorage.setItem("user", JSON.stringify(state.currentUser))
}, [state.currentUser])
return (
<AuthContext.Provider value={{current: state.current, dispatch}}>
{children}
</AuthContext.Provider>
)
}
I do not know what could be causing this problem but I have an idea that it has something to do with the state because it was redirecting well before I started combining it with the state. What could be the problem
CodePudding user response:
Issue
From that I can see, the App
isn't destructuring the correct context value to handle the conditional route protection.
The AuthContextProvider
provides a context value with current
and dispatch
properties
<AuthContext.Provider value={{ current: state.current, dispatch }}>
{children}
</AuthContext.Provider>
but App
is accessing a currentUser
property, which is going to be undefined because state.current
is undefined.
const { currentUser } = useContext(AuthContext);
const RequireAuth = ({ children }) => {
return currentUser ? children : <Navigate to="/login" />;
};
The Navigate
component will always be rendered.
Solution
Assuming the handleLogin
handler correctly updates the state then the solution is to be consistent with state properties.
<AuthContext.Provider value={{ currentUser: state.currentUser, dispatch }}>
{children}
</AuthContext.Provider>
...
const { currentUser } = useContext(AuthContext);
const RequireAuth = ({ children }) => {
return currentUser ? children : <Navigate to="/login" />;
};