I want to learn the correct approach for firebase routing authetication,what I mean is:
function App() {
const auth = getAuth();
if (!auth) {
<Spinner />;
}
return (
<>
{auth && (
<Router>
<Routes>
<Route path='/' element={<PrivateRoute />}>
<Route index element={<Explore />} />
<Route path='/offer' element={<Offers />} />
<Route path='/profile' element={<Profile />} />
</Route>
<Route path='/sign-in' element={<SignIn />} />
<Route path='/sign-up' element={<SignUp />} />
<Route path='/forgot-password' element={<ForgotPassword />} />
</Routes>
<Navbar />
</Router>
)}
I have this code-block, at first I thought I should've gotten a useAuth hook which use onAuthStateChanged, but I realize that this auth variable from getAuth is kinda work the same way so why not use it instead of a hook?
and my PrivateRoute looks like this:
function PrivateRoute() {
const currentUser = getAuth().currentUser;
return currentUser ? <Outlet /> : <Navigate to='/sign-in' />;
}
the problem is once app mounts, because of there is no app-level state, it stays the same.
Then if I try to log off and put some logic into routing like if user exists, dont' allow routing to signup or signin, it doesn't work.
If I use redux or context API, I would dispatch whenever I login, logout, signup but without them what is the correct set-up for handling this kind of routing?
after some time, i just figured out how to do what I needed to do, so I post it here just in case someone also encounters this problem and seeks help.
If you don't use redux or context api and still want to implement this kind of feature, here's how I do:
So what I needed to do? I want authenticate through firebase, what you need to do -at least what I found- implement a APP/LEVEL/STATE in order to check if user logged in or not and update the app level rendering, that renders the whole app and enforces behaviour accordingly.
So:
function App() {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setIsLoading(true);
if (user) {
setUser(user);
} else {
setUser(null);
}
setIsLoading(false);
});
return unsubscribe;
}, []);
if (isLoading) return <Spinner />;
return (
<>
<Router>
<Routes>
<Route path='/' element={<PrivateRoute user={user} />}>
<Route index element={<Explore />} />
<Route path='/offers' element={<Offers />} />
<Route path='/profile' element={<Profile />} />
<Route path='/create-listing' element={<CreateListing />} />
<Route path='/category/:categoryName' element={<Category />} />
</Route>
<Route
path='/sign-in'
element={!user ? <SignIn /> : <Navigate to='/' />}
/>
<Route
path='/sign-up'
element={!user ? <SignUp /> : <Navigate to='/' />}
/>
<Route
path='/forgot-password'
element={!user ? <ForgotPassword /> : <Navigate to='/' />}
/>
</Routes>
<Navbar />
</Router>
<ToastContainer autoClose={1000} />
</>
);
}
export default App;
Here, basically after component mounts, we want useEffect to only execute once and set up listener for auth state changing, whenever auth state changes like sign-in or log-out, the code block of onAuthStateChanged runs and when it runs, every time it sets the user if there's one or not, therefore re-renders the component which is App itself, and below the routing works accordingly. If you have a private route as I do, you can just pass the user as a prop, then:
import { Navigate, Outlet } from 'react-router-dom';
function PrivateRoute({ user }) {
return user ? <Outlet /> : <Navigate to='/sign-in' />;
}
export default PrivateRoute;
Related
I am encountering a problem with my private routing setup. Currently, I use the user variable in the App.js to determine if a user is logged in or not, in order to restrict access to private routes. The issue with this method is that if a user attempts to directly access a private page (such as "mysolutions"), they will be immediately redirected to the homepage due to the delay in fetching the user data from the database during the initial website load.
I would like to know how can I fix this issue.
My App.js code:
import React, { Suspense } from "react"
import { Navigate, Route, Routes } from "react-router-dom"
import rocketLoader from "./assets/animated_illustrations/rocketLoader.json"
import Layout from "./components/layouts/Layout"
import Meta from "./components/meta/Meta"
import LottieAnimation from "./components/reusable/LottieAnimation"
import ScrollToTop from "./components/reusable/ScrollToTop"
import { useAuthContext } from "./hooks/useAuthContext"
import "./App.css"
// lazy loading components
const Homepage = React.lazy(() => import("./pages/Homepage"))
const Dashboard = React.lazy(() => import("./pages/Dashboard"))
const MySolutions = React.lazy(() => import("./pages/MySolutions"))
const App = () => {
const { authIsReady, user } = useAuthContext()
return (
<>
<Meta routes={routes} />
<div>
<Suspense
fallback={
<div className="flex justify-center items-center min-h-screen">
<LottieAnimation animationDataFile={rocketLoader} />
</div>
}
>
<ScrollToTop>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Homepage />} />
<Route path="challenges" element={<Dashboard />} />
<Route
path="mysolutions"
element={user ? <MySolutions /> : <Navigate to="/" />}
/>
<Route path="*" element={<Navigate to="/" replace />} />
</Route>
</Routes>
</ScrollToTop>
</Suspense>
</div>
</>
)
}
export default App
You can use authIsReady variable from useAuthContext() for check the current user data inside the private route.
And with this variable you can simply add if condition to private route like :
<ScrollToTop>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Homepage />} />
<Route path="challenges" element={<Dashboard />} />
{authIsReady && (
<Route
path="mysolutions"
element={user ? <MySolutions /> : <Navigate to="/" />}
/>
)}
<Route path="*" element={<Navigate to="/" replace />} />
</Route>
</Routes>
</ScrollToTop>
I would create a loading page, which is shown to the user until the fetch is completed and we are able to decide if we can let advance to the private route or not. Would this solution work for you?
So I'm using react router v6 in my React App.
I have the following routes enabled in my app.js file
<Routes>
<Route path='/' component={<Home />} />
<Route path='/login' component={<SignUp />} />
<Route path='/signup' component={<Login />} />
</Routes>
Everything's fine and that. What I want to to do is to put restriction on pages. Now I know how to create PrivateRoutes and PublicRoutes based on LoggedIn User.
For this purpose I want the user to not be able to access Homepage after he or she signups.
Are there an functions for that or what strategy would I use.
I accomplished this using 'react-router-dom' and creating a PrivateRoute component. The following code is not tested but can give you some ideas. LoaderComponent is a loading animation of your choice can be toher component as well.
// Based on https://reactrouter.com/web/example/auth-workflow
// If the user is not yet authenticated.
const PrivateRoute: React.FC<PrivateRouteProps> = ({ children, path, ...props }) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
//put your authentication logic
setIsAuthenticated(true);
}, []);
return (
<Route
path={path}
{...props}
render={() => (isAuthenticated ? children : <LoaderComponent />)}
/>
);
};
And in your router config
import { Switch } from 'react-router-dom';
<Switch>
<PrivateRoute exact path='/'>
<Home />
</PrivateRoute>
...
</Switch>
I am trying to build a full stack application with User login/logout functionality.
I want to protect certain pages such that they can only be viewed when the user is logged in. For login I have created a REST API and I am using session storage to keep track of whether the user is logged in or not.
validateUser = () => {
let user = {
username: this.state.email,
password: this.state.password,
//status: "LOGGED_IN"
};
UserService.authenticateUser(user).then((res) => {
if(res.data === 'SUCCESS') {
window.sessionStorage.setItem("isUserLogged", true);
} else if(res.data === 'FAILURE') {
window.sessionStorage.setItem("isUserLogged", false);
this.resetLoginForm();
this.setState({"error":"Invalid username or password"});
}
})
};
Tis is my App.js
function App() {
return (
<div>
<Router>
<HeaderComponent/>
<div className="container">
<Switch>
<Route path="/" exact component={LandingPageComponent}></Route>
{/* <Route path ="/customers" component = {ListCustomerComponent}></Route> */}
{/* <Route path ="/add-customer/:id" component = {CreateCustomerComponent}></Route> */}
<Route path = "/view-customer/:id" component = {ViewCustomerComponent}></Route>
<Route path = "/admin-login" component = {AdminLoginComponent}></Route>
<Route path = "/admin-register" component = {AdminResgisterComponent}></Route>
<Route path="/customers" exact render={() => (
window.sessionStorage.getItem("isUserLogged") === "true"
? <ListCustomerComponent />
: <Redirect to='/admin-login' />
)} />
<Route path="/add-customer/:id" exact render={() => (
window.sessionStorage.getItem("isUserLogged") === "true"
? <CreateCustomerComponent />
: <Redirect to='/admin-login' />
)} />
</Switch>
</div>
<FooterComponent/>
</Router>
</div>
);
}
export default App;
Everything works fine if I don't check my session storage. But when I try to implement the conditional routes as shown above I start getting errors.
If I just put simple routes, then I don't encounter this error.
Any help would be highly appreciated.
You didn't pass Route props into your component. So history does not included in props, you can console.log(this.props) to check what this.props contains.
To fix it, let's pass Route props into your components
<Route path="/add-customer/:id" exact render={(props) => (
window.sessionStorage.getItem("isUserLogged") === "true"
? <CreateCustomerComponent {...props} /> // ADD PROPS HERE
: <Redirect to='/admin-login' />
} />
You didn't show what you did on ListCustomerComponent.
You could try to encapsulate your component using HOC withRouter or if you are using Functional component, use useHistory hook.
// on export class component
export default withRouter(YourComponent)
in functional component, you can use
const YourComponent = ()=>{
const history = useHistory();
// then you can say something such as
// history.push(...)
return <>...your view here...</>
}
export default YourComponent;
<Switch>
{/* Login Sections goes Here */}
<Route exact path='/' component={MainPage} />
<Route exact path='/login' component={Login} />
<Route exact path='/admin/' component={LoginAdmin} />
<Route exact path='/register' component={Register} />
{/* AdminUser ROutes goes here */}
<SuperUserDashboard>
<Route exact path='/admin/dashboard' component={Dashboardpage} />
<Route exact path='/admin/users' component={UsersAdmin} />
</SuperUserDashboard>
<Route exact path='' component={Notfound} />
</Switch>
in superuser dashboard check if user is authenticated if not redirect to admin login page else all the routes will be visible
I have built a CRUD application with react, redux and react-router. Application is redirecting to /login, if page is refreshed from PrivateRoutes. I am updating login status of user by fetching data from localStorage and dispatching action to update redux in App component.
App.js
const updateLoginStatus = () => {
if (checkIfUserLoggedIn()) {
const user = getLoginInitialState();
dispatch(updateLoginInitialState(user));
}
};
useEffect(() => {
updateLoginStatus();
}, [login.isLoggedIn]);
Routing in App component
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/login" component={Login} />
<Route exact path="/register" component={Register} />
<Route exact path="/logout" component={Logout}></Route>
<PrivateRoute exact path="/expenses" component={ListExpensesComponent} />
<PrivateRoute exact path="/expenses/add-expense" component={AddExpenseComponent} />
<PrivateRoute
exact
path="/expenses/edit-expense/:expense_id"
component={EditExpenseComponent}
/>
</Switch>
</Router>;
PrivateRoute.js
const login = useSelector((state) => state.loginReducer);
return (
<div>
<Route
{...rest}
render={(props) => {
if (!login.isLoggedIn) {
return <Redirect to="/login" />;
}
return <Component {...props} />;
}}></Route>
</div>
);
One solution I found is
adding one more action in login UPDATE_LOGIN_REQUEST and add loading property in login Reducer and set loading: true . Thus, displaying <Loader /> until login status is updated.
Is there any other way we can handle it? Thanks in advance.
Actually I had no problem with directing to another route by clicking a button, but somehow I can't direct manually by changing the URL. Every time I was about changing the URL (ex: localhost:3000/proposal), it always directs me to the first Route. Here's the Route in order :
<Switch>
<Route exact path="/" component={Landing} /> // => always goin here
<Route exact path="/login" component={Login} />
<Route exact path="/register" component={Register} />
{/* Dashboard */}
<PrivateRoute
exact
path="/home"
component={Home}
StickyNav={StickyNavbar}
/>
<PrivateRoute
exact
path="/proposal"
component={Proposal}
StickyNav={StickyNavbar}
/>
<PrivateRoute
exact
path="/laporan"
component={Laporan}
StickyNav={StickyNavbar}
/>
<Route component={NotFound} />
</Switch>
It doesn't direct me to Landing if I change the URL to non-private route. Here's my private route code :
import React from "react"
import { Route, Redirect } from "react-router-dom"
import { connect } from "react-redux"
import PropTypes from "prop-types"
const mapStateToProps = state => ({
auth: state.auth
})
const PrivateRoute = ({ component: Component, auth, ...rest }) => (
<Route
{...rest}
render={props =>
auth.isAuthenticated === true ? (
<Component {...props} {...rest} />
) : (
<Redirect to="/login" />
)
}
/>
)
PrivateRoute.propTypes = {
auth: PropTypes.object.isRequired
}
export default connect(mapStateToProps)(PrivateRoute)
Based on #zhuber said, the auth object from react-redux doesn't call before the private route was called. So I changed the condition from isAuthenticated using localStorage like this :
!isEmpty(localStorage.jwtToken) ? (
<Component {...props} {...rest} />
) : (
<Redirect to="/login" />
)