My app conditionally renders a login page or a Home Screen depending on whether a user is null or not. However, the app renders after Firebase has authenticated the user, so I can't seem to render the home screen page even though the user is logged in:
import React, { useEffect, useState} from 'react';
import './App.css';
import HomeScreen from './screens/HomeScreen';
import LoginScreen from './screens/LoginScreen';
import {auth} from "./firebase"
import {
BrowserRouter as Router,
Routes,
Route,
} from "react-router-dom"
import {useDispatch, useSelector} from "react-redux"
import {logout, login, selectUser} from "./features/userSlice"
function App() {
const user = useSelector(selectUser);
const dispatch = useDispatch();
useEffect(()=>{
const unsubscribe = auth.onAuthStateChanged(
(userAuth)=>{
if(userAuth){
dispatch(login({
uid: userAuth.uid,
email: userAuth.email
})
);
console.log(userAuth.email);
}
else{
dispatch(logout)
}
});
return unsubscribe;
}, []);
console.log(user);
return (
<div className="app">
<Router>
{!user? (
<LoginScreen></LoginScreen>
): (
<Routes>
<Route path="/" element={<HomeScreen />} />
<Route path="/test" element={<h1>hello world</h1>} />
</Routes>
)}
</Router>
</div>
);
}
export default App;
How am I able to render the app AFTER firebase authenticates the user, and stores user information into a slice? Only then will a selector to that data in the slice return as non null.
Related
An unauthenticated user can still see the "/account" page for a few seconds I mean there is still a delay of a few seconds before the unauthenticated user is completely redirected to another page. even though I have used ProtectedRoute, how can I make unauthenticated users unable to see the "/account" page at all without delay? i use react-router-dom 6.6.1 and firebase auth
here is for the app.js
import { ThemeProvider } from '#mui/material/styles';
import React from 'react';
import { BrowserRouter as Router, Route, Routes, Navigate} from 'react-router-dom'
import Home from './components/Home'
import Account from './components/Account';
import Signin from './components/Home/Signin';
import theme from './theme';
import { AuthContextProvider } from './components/Context/AuthContext';
import ProtectedRoute from './components/ProtectedRoute';
function App() {
return (
<ThemeProvider theme={theme}>
<AuthContextProvider>
<Router>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/signin' element={<Signin />} />
<Route path='/account' element={<ProtectedRoute><Account /></ProtectedRoute>} />
</Routes>
</Router>
</AuthContextProvider>
</ThemeProvider>
)
}
export default App
and this is for the ProtectedRoute.js
import React from 'react';
import { Navigate } from 'react-router-dom';
import { UserAuth } from './Context/AuthContext';
const ProtectedRoute = ({ children }) => {
const { user } = UserAuth();
if (!user) {
return <Navigate to='/' />;
}
return children;
};
export default ProtectedRoute;
this if for the UserAuth :
import { createContext, useContext, useEffect, useState } from 'react';
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
onAuthStateChanged,
} from 'firebase/auth';
import { auth } from '../../firebase';
const UserContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState({});
const createUser = (email, password) => {
return createUserWithEmailAndPassword(auth, email, password);
};
const signIn = (email, password) => {
return signInWithEmailAndPassword(auth, email, password)
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
});
return () => {
unsubscribe();
};
}, []);
return (
<UserContext.Provider value={{ createUser, user, signIn }}>
{children}
</UserContext.Provider>
);
};
export const UserAuth = () => {
return useContext(UserContext);
};
I want to redirect unauthenticated users to the main page without delay
it’s probably due to the fact that your guard is validating off of !user however your initial auth state has user as {}. In this case an empty object will validate as truthy.
You could either set the initial state of user as null or you could update th guard validation to check for a value on user like if (!user.id) …
Render Protected and Base routes conditional so unauthenticated user never have access of Protected routes.
Use the following code.
Always make separate file for routing for better code readability and Separation of concerns.
BaseRoutes.js
import React from 'react'
import { Route } from 'react-router-dom'
import Home from './components/Home'
import Signin from './components/Home/Signin';
const BaseRoutes = (
<>
<Route path='/' element={<Home />} />
<Route path='/signin' element={<Signin />} />
</>
)
export default BaseRoutes
ProtectedRoutes.js
import React from 'react';
import { Route } from 'react-router-dom';
import Account from './components/Account';
const ProtectedRoutes = () => {
return (
<>
<Route path='/account' element={<Account />} />
</>
)
};
export default ProtectedRoutes;
Routes.js
import React from 'react'
import { Navigate, Route, Routes, BrowserRouter } from 'react-router-dom'
import { UserAuth } from './Context/AuthContext';
import ProtectedRoutes from './components/ProtectedRoutes';
import BaseRoutes from './components/BaseRoutes';
const Router = () => {
const { user } = UserAuth();
const isUser = Boolean(user)
const redirectUrl = isUser ? '/account' : '/signin'
return (
<BrowserRouter>
<Routes>
{isUser ? <BaseRoutes /> : <ProtectedRoutes />}
<Route
path="*"
element={<Navigate to={redirectUrl} />}
/>
</Routes>
</BrowserRouter>
)
}
export default Router
app.js
import React from 'react';
import { AuthContextProvider } from './components/Context/AuthContext';
import { ThemeProvider } from '#mui/material/styles';
import theme from './theme';
import Router from './routes'
function App() {
return (
<ThemeProvider theme={theme}>
<AuthContextProvider>
<Router />
</AuthContextProvider>
</ThemeProvider>
)
}
export default App
I have 2 components both are exactly the same. one is redirected to when I click on a Navlink inside of my navbar that I created using react-bootstrap. The other component that is exactly the same just redirects to localhost:3000 and not "./member" when I click on the html button that should redirect to that component. Please help me.
the html button and the function to redirect look like
import {Link, Route, withRouter, useHistory} from 'react-router-dom'
const Posts = (props) => {
const dispatch = useDispatch();
const history = useHistory();
const getProfile = async (member) => {
// const addr = dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
history.push('/member')
}
return (
<div>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</div>
)
}
export default withRouter(Posts);
The routes.js looks like
const Routes = (props) => {
return (
<Switch>
<Route path="/member" exact component={Member} />
</Switch>
)
}
export default Routes
The component that I am trying to redirect to is exactly the same as one that is redirected to and working when I click on it from the navlink. I have downgraded to history 4.10.1
My index.js is
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import 'bootstrap/dist/css/bootstrap.min.css';
import { Router, Route } from 'react-router-dom';
import * as history from 'history';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import rootReducer from './reducers'
const store = createStore(rootReducer)
const userHistory = history.createBrowserHistory();
ReactDOM.render(
<Provider store = {store}>
<Router history={userHistory}>
<BrowserRouter>
<Route component={App} />
</BrowserRouter>
</Router>
</Provider>,
document.getElementById('root'));
serviceWorker.unregister();
When I wrap the app route in the url goes to ./member but it does not load.
<Switch>
<Route path="/" exact component={app} />
<Route path="/member" component={Member} />
</Switch>
<button onClick={() => history.push(`/${p.publisher}`)}>Profile</button>
I want to have a simple button that when clicked redirects a user to a route defined in my Index.tsx file.
When the button is clicked, the url bar is correctly changed to "/dashboard", however my component (just an h1) does not appear. If I reload chrome at that point it will appear.
Here's my code (Index.tsx):
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Route, Switch } from "react-router-dom";
import { Router } from "react-router";
import { createBrowserHistory } from "history";
import Dashboard from "./components/Dashboard";
const appHistory = createBrowserHistory();
ReactDOM.render(
<React.StrictMode>
<Router history={appHistory}>
<Switch>
<Route exact path="/" component={App} />
<Route path="/dashboard" component={Dashboard} />
</Switch>
</Router>
</React.StrictMode>,
document.getElementById("root")
);
Here's my start of a Login form (the first route above, App, renders it).
(Login.tsx):
import React, { useState } from "react";
import {Button} from "#material-ui/core";
import { useHistory} from "react-router-dom";
export const Login: React.FC = (props) => {
const classes = useStyles();
let history = useHistory();
const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const validateForm = (): boolean => {
return true;
};
const handleLoginClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
history.push("/dashboard");
};
return (
<form>
<div>
<Button
onClick={handleLoginClick}
color="inherit"
type="button"
>
Login
</Button>
</div>
</form>
);
};
export default Login;
Replace this line
import { Router } from "react-router"
with
import {BrowserRouter as Router } from "react-router-dom";
Try importing the Router from react-router-dom - the rest seems correct
import { Route, Router, Switch } from "react-router-dom";
react-router is mostly for internal usage - you only interact with react-router-dom
I have a Main component that looks like this:
import React from 'react';
import { Provider } from 'react-redux';
import { Router, Route } from 'react-router-dom';
import store from '../store';
import history from '../store/history';
import SideBar from './SideBar';
import { ConnectedUsers } from './UsersPage';
const Main = () => (
<Router history={history}>
<Provider store={store}>
<div>
<SideBar/>
<Route
exact
path="/users"
render={ () => (<ConnectedUsers/>)}
/>
</div>
</Provider>
</Router>
);
export default Main;
It uses a navigation component which I've named SideBar:
import { Link } from 'react-router-dom';
import React from 'react';
const SideBar = () => (
<div>
<Link to="/users">
<h1>Users</h1>
</Link>
</div>
);
export default SideBar;
This doesn't work. When I click on the Users link on the page in the browser, the URL changes to the correct one (localhost:8080/users) but the page remains blank. The component doesn't render. If I put the URL localhost:8080/users manually in the address bar in the browser the component renders correctly.
After some searches, I found people recommending to implement the routes like this:
import React from 'react';
import { Provider } from 'react-redux';
import { Router, Route, withRouter } from 'react-router-dom';
import store from '../store';
import history from '../store/history';
import SideBar from './SideBar';
import { ConnectedUsers } from './UsersPage';
const Main = () => (
<Router history={history}>
<Provider store={store}>
<div>
<SideBar/>
<Route exact path="/users" component={withRouter(ConnectedUsers)}
/>
</div>
</Provider>
</Router>
);
export default Main;
But this also didn't work for me. The exact same propblem remains—the /users page renders correctly when I manually type in the URL in the address bar, but comes up blank if I navigate to that URL using the Link.
I have a small react app which has user login, I want to have a private route which will be protected, and only authenticated users can use it.
Here is my code.
App.js
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
// Styling
import './App.css';
// Privat route
import PrivateRoute from './routes/PrivateRoute';
// Common
import Navigation from './components/common/Navigation';
// Components
import Layout from './components/Layout'
import Login from './components/Login';
import Home from './components/Home';
import Profile from './components/Profile';
export default () => (
<Router>
<Switch>
<Layout>
{/* Public routes */}
<Route exact path="/" component={ Home } />
<Route exact path="/login" component={ Login } />
{/* Private routes */}
<PrivateRoute exact path="/profile" component={ Profile } />
</Layout>
</Switch>
</Router>
);
PrivateRoute.js
import React, { Component } from 'react';
import { Route, Redirect } from 'react-router-dom';
import UserService from '../services/User';
export default class PrivateRoute extends React.Component {
state = {
isAuthenticated: null,
user: null
}
async componentDidMount(){
let isAuthenticated, user = null;
try{
user = await UserService.auth();
isAuthenticated = true;
}catch(e){
isAuthenticated = false;
}
this.setState({ isAuthenticated, user });
}
render() {
const { isAuthenticated, user } = this.state;
if(isAuthenticated === null){
return null;
}
return (
isAuthenticated ?
<Route path={this.props.path} component={this.props.component}/> :
<Redirect to={{ pathname: '/login', state: { from: this.props.location }}} />
);
}
}
Home.js
import React, { Component } from 'react';
export default class Home extends Component{
render(){
return (
<div>Home!</div>
);
}
}
I am constantly getting the error
Warning: You tried to redirect to the same route you're currently on: "/login"
even though I am in the home page which is has no business checking if user is authenticated.
I suspect this issue has something to do with my routes and the common layout I am trying to implement, but I cant find the problem.