Firebase onAuthStateChanged with React Context Not Updating App - reactjs

The problem I'm running into is that currentUser in App.js is null when <PrivateRoute /> renders, which will cause it to render a <Redirect /> and navigate to /login.
However, if I view the React Dev Tools I can see that the value field in Context.Provider does correctly have a user object set.This prop should rerender the rest of the app but it does not.
Why doesn't currentUser updating update the routes?
Auth.js
import React, { useEffect, useState } from 'react';
import firebase from 'firebase';
export const AuthContext = React.createContext();
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
useEffect(() => {
firebase.auth().onAuthStateChanged(setCurrentUser);
}, []);
return (
<AuthContext.Provider value={{ currentUser }}>
{children}
</AuthContext.Provider>
);
};
App.js
import { AuthProvider } from './Auth';
import { AuthContext } from './Auth';
function App() {
const PrivateRoute = ({ component: Component, ...rest }) => {
const { currentUser } = useContext(AuthContext);
return (
<Route
{...rest}
render={props =>
currentUser ? (
<Component {...props} {...rest} />
) : (
<Redirect to={{ pathname: '/login' }} />
)
}
/>
);
};
return (
<AuthProvider>
<Router history={createBrowserHistory()}>
<Switch>
<PrivateRoute path="/" exact component={HomeScreen} />
<Route path="/login" exact component={LogIn} />
</Switch>
</Router>
</AuthProvider>
);
}

Related

Is there any sense to useContext in React?

Hello i need help with my app.
What sense is to put my User data to useContext if when i refresh page all data disapear?
How to make this context permament?
I have been sitting with this all day, and when i try to get data in HomePage, everything is ok until i refresh page.
The second question is about JWT. It's used only on server side, right? It's verifing the token when I'm making server reqest only?
below is my code
App.js
import Navbar from "./components/Navbar";
import AddNewAnnoucement from "./components/pages/AddNewAnnoucement/AddNewAnnoucement";
import { Route, Routes } from 'react-router-dom';
import Home from "./components/pages/Home";
import Annoucements from "./components/pages/Annoucements/Annoucements";
import Register from "./components/pages/Register/Register";
import Login from "./components/pages/Login/Login";
import { useState, useMemo, useEffect, createContext } from "react";
import { getProfile, tokenIsValid } from './service/userService.js'
export const UserContext = createContext();
function App() {
const [userData, setUserData] = useState({
token: undefined,
user: undefined,
})
useEffect(() => {
const isLoggedIn = async () => {
let token = localStorage.getItem("token")
if (token == null) {
localStorage.setItem("token", "")
token = ""
}
const tokenResponse = tokenIsValid(token);
tokenResponse.then((res) => {
if (res.data) {
const userResponse = getProfile(token);
userResponse.then((res) => {
setUserData({
token: token,
data: res.data
})
})
}
}).catch((err) => {
console.log(err);
}
)
}
isLoggedIn();
}, [])
return <>
<UserContext.Provider value={{ userData, setUserData }}>
<Navbar />
<div className='container'>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/annoucements' element={<Annoucements />} />
<Route path='/annoucements/add' element={<AddNewAnnoucement />} />
<Route path='/register' element={<Register />} />
<Route path='/login' element={<Login />} />
</Routes>
</div>
</UserContext.Provider>
</>
}
export default App;

How to create Private Routes with firebase v9 reactjs [duplicate]

This question already has an answer here:
React-Router-Dom unable to render page but routes back due to PrivateRoute
(1 answer)
Closed 8 months ago.
My problem is that the moment i navigate to the homepage and the user is not authenticated the page shows for a split second and then move on to the login page. I want it to redirect to login only and not show the homepage for a split second
I already created a private route in my project but for a split second the protected routes shows when i navigate on to it.
here is my code:
AuthContextProvider
const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
});
return () => unsubscribe();
}, []);
const value = { user };
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
PrivateRoute.js
const PrivateRoute = ({ children }) => {
let { user } = useAuth();
if (user) {
return <Outlet />;
} else {
return <Navigate to="/login" />;
}
};
App.js
function App() {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/" element={<Layout />}>
<Route element={<PrivateRoute />}>
<Route path="/" element={<Home />} />
<Route path="/home" element={<Home />} />
<Route path="/reminders" element={<Reminders />} />
<Route path="/archive" element={<Archive />} />
<Route path="/trash" element={<Trash />} />
</Route>
</Route>
</Routes>
);
}
Loginpage.js
const LoginPage = () => {
const { user } = useAuth();
const navigate = useNavigate();
const signIn = async () => {
const provider = new GoogleAuthProvider();
await signInWithRedirect(auth, provider);
};
useEffect(() => {
onAuthStateChanged(auth, (currentUser) => {
if (currentUser) {
navigate("/");
}
});
}, []);
return (
<>
<button
onClick={signIn}
className="bg-blue-500 p-3 text-white hover:bg-blue-600"
>
Sign In With Google
</button>
</>
);
};
Code for hindering the Loggedin User to go to the Login Route
I have created a SpecialRoute which will redirect the loggedIn User to the mainpage if the user tries to go the login page.
SpecialRoute.js
import { Login } from '../pages/Login';
import { useAuth } from '../firebase-config';
import React from 'react';
import { Outlet, Navigate } from 'react-router-dom';
// import { useAuth } from '../firebase-config';
export const SpecialRoute = () => {
const user = useAuth();
return user ? <Outlet /> : <Navigate to="/" replace />;
};
App.js
<Route element={<SpecialRoute />}>
<Route path="/login" element={<Login />} />
</Route>
In your Private Route Component, do this :-
const PrivateRoute = ({ children }) => {
let { user } = useAuth();
return typeof user === 'undefined' ? (
<h1>Loading.....</h1>
) : user ? (
<Outlet />
) : (
<Navigate to="/login" />
);
};
Below is how I created my private routes (in Firebase v9):-
useAuth Hook
// custom hook
export function useAuth() {
//
const [currentUser, setCurrentUser] = useState<any>();
useEffect(() => {
const unSubscribe = onAuthStateChanged(auth, (user) =>
setCurrentUser(user)
);
return unSubscribe;
}, []);
return currentUser;
}
My PrivateRoute/ProtectedRouted Component
import { Login } from '../pages/Login';
import React from 'react';
import { Outlet } from 'react-router-dom';
import { useAuth } from '../firebase-config';
export const ProtectedRoute = () => {
const user = useAuth();
console.log('/////user autheticated', user);
return typeof user === 'undefined' ? (
<h1>Loading.....</h1>
) : user ? (
<Outlet />
) : (
<Login />
);
};
App.js
function App() {
return (
<>
<Router>
<Routes>
<Route element={<ProtectedRoute />}>
<Route path="/" element={<Home />} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<Signin />} />
</Routes>
</Router>
</>
);
}

Redirect not working with react router authenticated route

I'm attempting to create a private route based on the answers here, however it is not performing the redirect.
This is my private route component
import React, { Component, useContext } from "react";
import { Redirect, Route, RouteComponentProps, RouteProps } from "react-router-dom";
import { RootStoreContext } from "../stores/rootStore";
import { observer } from "mobx-react-lite";
const PrivateRoute: React.FC<RouteProps> = ({ children, ...rest }) => {
const rootStore = useContext(RootStoreContext);
const { isAuthenticated } = rootStore.msalStore;
return (
<Route
{...rest}
render={(props: RouteComponentProps<{}>) =>
isAuthenticated ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: "/", state: { from: props.location } }} />
)
}
/>
);
};
export default observer(PrivateRoute);
And this is my App.tsx
//Imports
const App: React.FC = () => {
return (
<>
<BrowserRouter>
<ThemeProvider theme={theme}>
<Route exact path="/" component={LoginPage} />
<Route path="/authcallback" component={AuthCallback} />
<PrivateRoute path="/home" component={HomePage} />
</ThemeProvider>
</BrowserRouter>
</>
);
};
export default App;
When I type /home in the browser, or create a test button that navigates to home, it loads the component even though isAuthenticated = false.
I know it's not related to the value of isAuthenticated in my mobx store as changing to const [isAuthenticated, setIsAuthenticated] = useState(false); doesn't work either. I also have no other instances of routing in my app.
What could be the issue here?
use Switch (from react-router-dom) to wrap routes,
import { Switch } from "react-router-dom";
const App: React.FC = () => {
return (
<>
<BrowserRouter>
<ThemeProvider theme={theme}>
<Switch>
<Route exact path="/" component={LoginPage} />
<Route path="/authcallback" component={AuthCallback} />
<PrivateRoute path="/home" component={HomePage} />
</Switch>
</ThemeProvider>
</BrowserRouter>
</>
);
};
export default App;
This discussion helped me
My solution...
https://codesandbox.io/s/modest-fire-6mj3i?file=/src/PrivateRoute.tsx:0-517
import * as React from "react";
import { BrowserRouter, Link, Route, Switch } from "react-router-dom";
import PrivateRoute from "./PrivateRoute";
import "./styles.css";
const Home: React.FC = () => {
return <div>Home</div>;
};
const Login: React.FC = () => {
return <div>Login</div>;
};
export default function App() {
return (
<BrowserRouter>
<ul>
<li>
<Link to="/home">Home</Link>
</li>
<li>
<Link to="/">Login</Link>
</li>
</ul>
<Switch>
<Route exact path="/" component={Login} />
<PrivateRoute redirectPath="/login" path="/home" component={Home} />
</Switch>
</BrowserRouter>
);
}
import { Redirect, Route, RouteProps } from "react-router";
import React, { useState } from "react";
export interface IPrivateRouteProps extends RouteProps {
redirectPath: string;
}
const PrivateRoute: React.FC<IPrivateRouteProps> = (props) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
return isAuthenticated ? (
<Route {...props} component={props.component} render={undefined} />
) : (
<Redirect to={{ pathname: props.redirectPath }} />
);
};
export default PrivateRoute;
If I click Home I get redirected to /login and the Home component is hidden
useState is only used for testing the authenticated state from within the component.

How to restrict pages to logged in users only

I want to protect some urls so users can't access them without login in first. This is my Auth.js
export const AuthContext = React.createContext();
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
useEffect(() => {
firebase.auth().onAuthStateChanged(setCurrentUser);
}, []);
return (
<AuthContext.Provider
value={{ currentUser }}
>
{children}
</AuthContext.Provider>
);
};
And this is my App.jsx
function App() {
return (
<AuthProvider>
<Router>
<div>
<Route path="/citas" exact>
<CitasPage />
</Route>
<Route path="/citasDetalladas" exact>
<CitaDetallada />
</Route>
<Route path="/crearCita" exact>
<CrearCitasPage />
</Route>
<PrivateRoute path="/index" exact >
<Index />
</PrivateRoute>
</div>
</Router>
</AuthProvider>
);
}
export default App;
Since I'm new to React, I've been following a tutorial for doing the login and logout using Firebase and React, but it didn't help with this issue. Any ideas?
You can use this auth flow to protect routes from unauthenticated users.
I have created global context with maintain currentUser.
By this currentUser, routes are decided.
AuthProvider.js
import React, { createContext, useState, useEffect } from "react";
import firebase from "./firebase";
export const AuthContext = createContext();
export function AuthProvider({ children }) {
useEffect(() => {
setIsLoading(true);
firebase.auth().onAuthStateChanged((user) => {
if (user) {
setCurrentUser(user);
setIsLoading(false);
} else {
setIsLoading(false);
setCurrentUser(null);
}
});
}, [setCurrentUser, setRole, setIsLoading]);
return (
<AuthContext.Provider value={{ currentUser }}>
{children}
</AuthContext.Provider>
);
App.js
import React from "react";
import { HashRouter as Router } from "react-router-dom";
import { AuthProvider } from "./data/auth";
function App() {
return (
<AuthProvider>
<Router>
<Routes />
</Router>
</AuthProvider>
);
}
export default App;
Routes.js
import React, { useContext } from 'react'
import { Switch, Redirect, Route } from 'react-router-dom';
import { AuthContext } from "../data/auth";
const Routes = () => {
const { currentUser } = useContext(AuthContext);
if (currentUser) {
return (
<Switch>
<Route path='/dashboard' component={dashboard} />
<Route path='/report' component={report} />
<Route path='/profile' component={profile} />
<Redirect to='/report' />
</Switch>
)
} else {
return (
<Switch>
<Route path='/login' component={login} />
<Route path='/register' component={register} />
<Redirect to='/login' />
</Switch>
)
}
}
export default Routes;
You need to do something to determine whether the user is currently authenticated. I'm not sure exactly how firebase.auth() works but you need to check on each route (or use a higher-order component) whether the user is authenticated and if not, return <Redirect to='somewhereElse' />

I can't access a private route using firebase

I am creating a private route to be accessed only when the firebase listener verifies that the user is logged in, but I cannot access this route the way I am doing.
const PrivateRoute = ({ component: Component, ...rest }) => {
let autenticado = false;
firebase.auth().onAuthStateChanged((user) => {
if (user) {
autenticado = true;
} else {
autenticado = false;
}
});
return (
<Route
{...rest}
render={(props) =>
autenticado ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: "/", state: { from: props.location } }} />
)
}
/>
);
};
const Routes = () => (
<BrowserRouter>
<Switch>
<Route path="/" exact component={Login} />
<PrivateRoute path="/home" component={Home} />
</Switch>
</BrowserRouter>
);
export default Routes;
Alvaro's solution is good, but Robert Terrell brought up a good point:
if you refresh your app, the private route kicks you back to the login page because it takes a few moments for the currentUser to re register
.
To fix this, id wait for the authcontext to load before displaying the site on line 24 in AuthContext.tsx:
import React, { useEffect, useState } from "react";
import {auth, firebase} from "../firebaseSetup";
type ContextProps = {
user: firebase.User | null;
};
export const AuthContext = React.createContext<Partial<ContextProps>>({});
export const AuthProvider = ({ children }: any) => {
const [user, setUser] = useState(null as firebase.User | null);
const [loading, setLoading] = useState(true);
useEffect(() => {
auth.onAuthStateChanged((user: any) => {
setUser(user);
setLoading(false);
});
}, []);
return (
<AuthContext.Provider
value={{user}}>
{!loading && children}
</AuthContext.Provider>
);
}
It's hard to tell where your code is going wrong without seeing other components. I do basically the same approach when I set up firebase auth and set up private routes. I bring my context into my PrivateRoute component like so:
import React, { useContext } from "react";
import { Route, Redirect } from "react-router-dom";
import { AuthContext } from "./Auth";
const PrivateRoute = ({ component: RouteComponent, ...rest }) => {
const {currentUser} = useContext(AuthContext);
return (
<Route
{...rest}
render={routeProps =>
!!currentUser ? (
<RouteComponent {...routeProps} />
) : (
<Redirect to={"/admin/login"} />
)
}
/>
);
};
My Auth.js file is where I create my context like so:
import React, { useEffect, useState } from "react";
import app from "./base.js";
export const AuthContext = React.createContext();
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
useEffect(() => {
app.auth().onAuthStateChanged(setCurrentUser);
}, []);
return (
<AuthContext.Provider
value={{
currentUser
}}
>
{children}
</AuthContext.Provider>
);
};
I initiate my firebase instance in a file called base.js that looks like so :
import * as firebase from "firebase/app";
import "firebase/auth";
console.log(process.env.REACT_APP_FIREBASE_DATABASE)
const app = firebase.initializeApp({
apiKey: process.env.REACT_APP_FIREBASE_KEY,
authDomain: process.env.REACT_APP_FIREBASE_DOMAIN,
databaseURL: process.env.REACT_APP_FIREBASE_DATABASE,
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID
});
export default app;
In your routes you would then add the AuthProvider
const Routes = () => (
<BrowserRouter>
<Switch>
<AuthProvider>
<Route path="/" exact component={Login} />
<PrivateRoute path="/home" component={Home} />
</AuthProvider>
</Switch>
</BrowserRouter>
);
export default Routes;

Resources