Why is react admin login page only shown for one second? - node.js

I have built a website with react admin. Now i want to add the basic login page from react admin.
For this I have added the simple authProvider which passes all username and password combinations. But now the login page is only shown for one second when I click on the logout button and then the website always jumps back to the dashboard.
I have tried a lot but can't find the error.
Maybe someone has an idea what it could be or had the same problem before?
Here is my code snippet from App.js:
function App() {
return(
<Admin
dashboard={Dashboard}
authProdiver={authProvider}
dataProvider={dataProvider}
customRoutes={customRoutes}
theme={theme}
layout={MyLayout}
>
<Resource
...
/>
...
</Admin>
);
}
export default App;
I added the basic authProvider from the tutorial:
const authProvider = {
// authentication
login: ({ username }) => {
localStorage.setItem('username', username);
// accept all username/password combinations
return Promise.resolve();
},
logout: () => {
localStorage.removeItem('username');
return Promise.resolve();
},
checkError: () => Promise.resolve(),
checkAuth: () =>
localStorage.getItem('username') ? Promise.resolve() : Promise.reject(),
getPermissions: () => Promise.reject('Unknown method'),
};
export default authProvider;
my own layout is:
MyLayout.js:
import React from 'react';
import TreeMenu from '#bb-tech/ra-treemenu';
import { Layout } from 'react-admin';
import MyAppBar from './MyAppBar';
import { ProfileProvider } from './MyProfile/Profile.js';
const MyLayout = (props) => (
<ProfileProvider>
<Layout {...props} appBar={MyAppBar} menu={TreeMenu} />
</ProfileProvider>
);
export default MyLayout;
MyAppBar.js:
import React from "react";
import { AppBar } from "react-admin";
import MyUserMenu from "./MyUserMenu";
const MyAppBar = props =>
<AppBar {...props}
userMenu={<MyUserMenu />}
/>;
export default MyAppBar;
MyUserMenu.js:
import React from 'react';
import { UserMenu, MenuItemLink} from 'react-admin';
import SettingsIcon from '#material-ui/icons/Settings';
const MyUserMenu = (props) => {
return (
<UserMenu {...props}>
<MenuItemLink
to="/my-profile"
primaryText="Mein Profil"
leftIcon={<SettingsIcon />}
/>
</UserMenu>
);
};
export default MyUserMenu;

Related

User disconnected for a few seconds when reloaded using AuthContext [duplicate]

This question already has answers here:
How to create a protected route with react-router-dom?
(5 answers)
Closed last month.
I am building an app using Node.js v18 and React v18 and I have the following problem.
I am redirecting my user when he tries to go to login but is already logged in. The problem is that for a short second, the login page is rendered.
AuthContext.tsx
import { User, getAuth, onAuthStateChanged } from "firebase/auth";
import React, { useState, useEffect, createContext, PropsWithChildren} from 'react';
export const AuthContext = createContext<User | null>(null);
export const AuthProvider = (props : PropsWithChildren) => {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
onAuthStateChanged(getAuth(), (currentUser) => {
setUser(currentUser);
})
}, []);
return <AuthContext.Provider value={user}>{props.children}</AuthContext.Provider>
}
App.tsx
import React, { useContext } from "react";
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import LoginPage from "./authentication/LoginPage";
import RegisterPage from "./authentication/RegisterPage";
import { AuthContext } from "./context/AuthContext";
import PaymentPage from "./payment/PaymentPage";
export default function App(){
const user = useContext(AuthContext);
function redirect(){
if(user !== null){
return <Navigate to="/pay" />;
}else{
return <LoginPage />;
}
}
return (
<BrowserRouter>
<Routes>
<Route path="/" element={redirect()}/>
<Route path="/register" element={<RegisterPage />}/>
<Route path="/pay" element={<PaymentPage />}/>
</Routes>
</BrowserRouter>
);
}
Any ideas on how to solve this problem ?
Thank you
Firebase automatically restores the user credentials from the local storage of the device, but during this process is makes a call to the server to check if those credentials are still valid, for example if the account hasn't been suspended. The onAuthStateChanged listener won't fire its first event until this asynchronous call has completed, which may indeed take a moment. Your UI uses the lack of a current user as a sign to show the login UI, which causes that UI to show briefly.
Typically you'll want to show some loading indicator while the check is going on, until you get the first onAuthStateChanged event. This is the simplest solution as you now handle all three cases: the user is not logged in, the user is logged in, and when we don't know yet whether the user is logged in.
Alternatively, you can store a value in local storage to indicate that the user was logged in before, and based on that assume that they will be logged in successfully again as Michael Bleigh showed in this I/O talk. Note though that in this case there's a change that your app thinks the user is logged in initially, but then turns out to be wrong - for example when the account was suspended - so you'll have to handle this flow by then redirecting back to the log-in page.
Thanks to #Frank van Puffelen I managed to implement a solution :
AuthContext.tsx
import { User, getAuth, onAuthStateChanged } from "firebase/auth";
import React, { useState, useEffect, createContext, PropsWithChildren} from 'react';
export const AuthContext = createContext<UserLogged>({user : null, loading: true});
//Add an interface to store the loading state
interface UserLogged {
user : User | null ;
loading : boolean;
}
export const AuthProvider = (props : PropsWithChildren) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
onAuthStateChanged(getAuth(), (currentUser) => {
setUser(currentUser);
//once the user has been set I now the loading is over
setLoading(false);
})
}, []);
return <AuthContext.Provider value={{user: user, loading: loading}}>{props.children}</AuthContext.Provider>
}
App.tsx
import React, { useContext } from "react";
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import LoginPage from "./authentication/LoginPage";
import RegisterPage from "./authentication/RegisterPage";
import { AuthContext } from "./context/AuthContext";
import PaymentPage from "./payment/PaymentPage";
import { Audio } from 'react-loader-spinner'
export default function App(){
const userLogged = useContext(AuthContext);
function redirect(){
if(userLogged.loading){
// add a loading indicator
return <Audio />;
}else{
if(userLogged.user !== null){
return <Navigate to="/pay" />;
}else{
return <LoginPage />;
}
}
}
return (
<BrowserRouter>
<Routes>
<Route path="/" element={redirect()}/>
<Route path="/register" element={<RegisterPage />}/>
<Route path="/pay" element={<PaymentPage />}/>
</Routes>
</BrowserRouter>
);
}

Browser Reload: Does not get back to selected page, goes to default homepage - ReactJs

I am new in using ReactJS and learning it bit by bit. I have 3 pages: homepage, contacts and moviesDetails. When I travel through contacts or moviesDetails and hit browser's reload, it gets me back to homepage which I do not want. I want it to stay on the same page which I am in.
If I am in contacts page, and hit browser's reload, I want it to stay on contacts page. I do not want it to go to homepage.
I don't know how to store the opened page's path in localStorage. I need help here as I cannot figure out where I am going wrong.
Following is App.js code.
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Homepage from "./homepage/Homepage";
import Contacts from "./contacts/Contacts";
import PrivateRoute from "./private-route/PrivateRoute";
import MoviesDetails from "./MoviesDetails/MoviesDetails";
class App extends Component {
render() {
return (
<Router>
<div className="App">
<Switch>
<PrivateRoute exact path="/homepage" component={Homepage} />
<PrivateRoute exact path="/contacts" component={Contacts} />
<PrivateRoute exact path="/moviesDetails" component={MoviesDetails} />
</Switch>
</div>
</Router>
);
}
}
export default App;
Following is contacts.js code: (All respective components are being imported)
import React, { Component } from "react";
import { connect } from "react-redux";
import Container from '#material-ui/core/Container';
import { changeHeaderName} from '../../actions/homepageActions';
class contacts extends Component {
constructor() {
super();
this.state = {
data:"",
value: 0,
date: "",
errorList: {}
};
}
componentDidMount() {
this.props.header("Contacts");
}
render() {
const { classes } = this.props;
return (
<Container>
<TabPanel value={this.state.value}>
<Grid container
justify="flex-start"
alignItems="center"
>
<Grid xs={6}>
<Typography>
(Names here)
</Typography>
</Grid>
</Grid>
<Grid xs={3}>
<Typography>
Contacts
</Typography>
</Grid>
<Grid xs={5}>
<Typography>
(All the contacts are listed here)
</Typography>
</Grid>
</TabPanel>
</Container>
);
}
}
const mapStateToProps = state => {
return {mainUser: state.auth.mainUser}
};
export default connect(mapStateToProps, {header})(configurations);
Following is store.js code:
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
const initialState = {};
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(thunk),
(b&&a()) ||compose)
);
export default store;
And store exported above is been used in index.js file.
Given the above code, I do not want my loaded page go back to the homepage. I want to stay on the same page. Browser reload gets me back to "/homepage" instead of "/contacts". Browser reload gets me back to "/homepage" instead of "/moviesDetails".
I am not using any hooks here. So I don't want my code to be in hooks. Just a simple react.js code.
EDIT NO: 1
Following is my PrivateRoute.js code
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import PropTypes from "prop-types";
const PrivateRoute = ({component: Component, authentic, ...rest}) => (
<Route
{...rest}
render={ props =>
authentic.isAuthenticated === true ? (
<div>
<Component {...props} />
</div>
) : (
<Redirect to="/" />
)
}
/>
);
PrivateRoute.propTypes = {
authentic: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
authentic: state.auth
});
export default connect(mapStateToProps)(PrivateRoute);
EDIT NO: 2
Following is redux store provider : (this is in index.js file)
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './components/App';
import store from "./store";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root'));
EDIT NO: 3
Following is MoviesDetails component
import React, { Component } from "react";
import Container from '#material-ui/core/Container';
import { connect } from "react-redux";
import Table from '#material-ui/core/Table';
import TableCell from '#material-ui/core/TableCell';
import TableHead from '#material-ui/core/TableHead';
import TableRow from '#material-ui/core/TableRow';
import TableBody from '#material-ui/core/TableBody';
import TableContainer from '#material-ui/core/TableContainer';
import { moviesList } from "./actions/moviesActions";
class MoviesDetails extends Component {
constructor() {
super();
this.state = {
skip: 0,
limit: 10,
pageNumber: 0,
value: '',
nameMovie:"",
genre:"",
ratings:"",
numberOfSongs:"",
releaseDate:"",
};
}
componentDidMount() {this.fetchRecords();}
fetchRecords=async()=>{
let payload ={
nameMovie:this.state.nameMovie,
genre:this.state.genre,
ratings:this.state.ratings,
numberOfSongs :this.state.numberOfSongs ,
releaseDate : this.state.releaseDate,
skip : this.state.limit * this.state.pageNumber,
limit: this.state.limit,
}
await this.props.moviesList(payload);
}
render() {
const { classes } = this.props;
return (
<div>
<div />
<Container >
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Movie Name</TableCell>
<TableCell>Genre</TableCell>
<TableCell>Song Count</TableCell>
<TableCell>Ratings</TableCell>
<TableCell>Release Date</TableCell>
</TableRow>
</TableHead>
<TableBody>
{this.props.movies.moviesList.map((movie, index) => {
return (
<TableRow >
<TableCell>
{nameMovie}
</TableCell>
<TableCell>{genre}</TableCell>
<TableCell>{numberOfSongs}</TableCell>
<TableCell>{ratings}</TableCell>
<TableCell>{releaseDate}</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</TableContainer>
</Container>
</div>
);
}
}
const mapStateToProps = state => {
return {
movie: state.movie,
adim: state.auth.admin,
}
};
export default connect(mapStateToProps, {moviesList })(MoviesDetails);
I handle my moviesDetails tab like this:
handleMovies = (e) => { e.preventDefault();
this.props.history.push("/moviesDetails"); }
You should persist your redux state to local storage when it updates, and initialize your store from local storage when app loads.
Minimal Redux Store Persistence Example:
Create a "middle" component to handle persisting state updates to localStorage.
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux];
const StorePersister = ({ children }) => {
const state = useSelector(state => state);
useEffect(() => {
localStorage.setItem('myState', JSON.stringify(state));
}, [state]);
return children;
};
index - wrap the App component with the store persister.
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './components/App';
import StorePersister from './components/StorePersister';
import store from "./store";
ReactDOM.render(
<Provider store={store}>
<StorePersister>
<App />
</StorePersister>
</Provider>,
document.getElementById('root')
);
Initialize state from local storage. If there is no "myState" key or the parsing returns null then the empty object ({}) will be used as a fallback.
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
const initialState = JSON.parse(localStorage.getItem('myState')) || {};
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(thunk),
(b&&a()) || compose
)
);
export default store;
There is also a redux-persist package out there that offers a bit of customization for what gets persisted to and initialized from the persistence.

How to use useEffect and the Context API to check if a user is logged in and protect a route?

I am trying to protect routes based on whether a user is logged in or not but I cannot get this to work properly since it seems that the information stored in my context provider is not available on the initial component load.
I am checking whether the user is authenticated within my App.js file by making a request to my node server through the useEffect hook. It tries to store this info within the context api which it successfully does but it appears that rendering other components will not wait for the context api to "catch up" or load first.
I am sure there is something simple I am missing or maybe I am using bad convention with checking if a user is authenticated. Any help would be appreciated!
App.js
import React, { useState, useEffect } from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import Axios from 'axios';
import Header from './components/layout/Header';
import Home from './components/pages/Home';
import HiddenPage from './components/pages/HiddenPage';
import Login from './components/auth/Login';
import Register from './components/auth/Register';
import UserContext from './context/UserContext';
import ProtectedRoute from './components/auth/ProtectedRoute';
import './style.scss';
export default function App() {
const [userData, setUserData] = useState({
token: undefined,
user: undefined,
});
useEffect(() => {
const checkLoggedIn = async () => {
let token = localStorage.getItem('auth-token');
if (token === null) {
localStorage.setItem('auth-token', '');
token = '';
}
const tokenResponse = await Axios.post(
'http://localhost:5000/users/tokenIsValid',
null,
{ headers: { 'x-auth-token': token } }
);
if (tokenResponse.data) {
const userResponse = await Axios.get('http://localhost:5000/users/', {
headers: { 'x-auth-token': token },
});
setUserData({
token,
user: userResponse.data,
});
}
};
checkLoggedIn();
}, []);
return (
<>
<BrowserRouter>
<UserContext.Provider value={{ userData, setUserData }}>
<Header />
<div className="container">
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/login" component={Login} />
<Route exact path="/register" component={Register} />
<ProtectedRoute path="/hidden" component={HiddenPage} />
</Switch>
</div>
</UserContext.Provider>
</BrowserRouter>
</>
);
}
ProtectedRoute.js
import React, { useContext } from 'react';
import { Redirect } from 'react-router-dom';
import UserContext from '../../context/UserContext';
export default function ProtectedRoute(props) {
const { userData } = useContext(UserContext);
const Component = props.component;
const isAuthenticated = !!userData.user;
console.log(isAuthenticated);
return isAuthenticated ? <Component /> : <Redirect to={{ pathname: '/' }} />;
}
add a loading state...
const [loading, setLoading] = useState(false)
after you check local storage and make axios call, update the state
if (loading) return null;
// else render the routes
return (
// the regular routes...
)
You basically want to account for a 3rd state.
Either:
there is a user
there is NO user
the effect has not yet completed
At whatever stage in your useEffect you can confirm that there is no user (unauthenticated) set the user to false.
Since undefined !== false, in your return you can test for both...
if (userData.user === undefined) return 'loading...'
Then you can use your ternary after this line with the knowledge that user has some value. Either false or some user object...

How to effectively protect routes in combination with react-router and passport on the backend

I have React and Node.js with passport.js on the backend which implements my app auth. My react makes a call to my backend and fetches the authorized user via action reducer. Everything works fine but there is a problem with the route guards. This is how I am protecting the routes if the user is not logged in
if(!this.props.auth) return
The problem is when the user is logged in, if page is refreshed, the code above executes faster than mapStateToProps returns the authorized user and the loggedIn user is redirected to the index page. This is bad user experience. Please help me how to resolve this issue and I would appreciate help and advice.
I think what I need to do is to ensure that store is updated first before DOM is rendered but I am not sure how to do it.
Here is dashboard
class Dashboard extends Component {
render() {
if(!this.props.auth) return <Redirect to='/' />
if (!this.props.auth.googleUsername) {
return <div className='container'> Loading ... </div>;
} else {
return (
<div className='container' style={{ margin: '10px 10px' }}>
{this.props.auth.googleUsername}
</div>
);
}
function mapStateToProps({auth}) {
return {auth};
}
export default connect(mapStateToProps)(Dashboard);
Here is App.js
import { connect } from 'react-redux';
import { fetchUser } from './store/actions/index';
import Home from './components/layout/Home';
import Dashboard from './components/layout/Dashboard';
class App extends Component {
componentDidMount() {
this.props.fetchUser();
}
render() {
return (
<div>
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path='/' component={Home} />
<Route path='/dashboard' component={Dashboard} />
</Switch>
</div>
</BrowserRouter>
</div>
);
}
}
export default connect(null,{ fetchUser })(App)
Action reducer
import axios from 'axios';
import { FETCH_USER } from './types';
export const fetchUser = () => async dispatch => {
const res = await axios.get('/api/current_user');
dispatch({ type: FETCH_USER, payload: res.data });
};
Auth Reducer
import { FETCH_USER } from '../actions/types';
export default function(state = false, action) {
switch (action.type) {
case FETCH_USER:
return action.payload;
default:
return state;
}
}
For those who has this issue, I managed to solve the probable. The issue was that I need to persist redux store across my app. I used a third party library called 'redux-persist'
Here is the set I used in my index.js
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './store/reducers/rootReducer';
import thunk from 'redux-thunk';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { PersistGate } from 'redux-persist/integration/react';
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
const store = createStore(persistedReducer, applyMiddleware(thunk));
const persistor = persistStore(store);
ReactDOM.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>,
document.getElementById('root'));

Losing currentUser when App component refreshes?

I am currently working on a simple web app using React, Node.js, passport-google-oauth20.
For some reason, whenever the application re-mounts, I lose the currentUser state. It's as if req.user is just lost.
The way it works is I first fetch the current user when the application mounts and set this.state.currentUser to the results. The issue is when the page refreshes. The component re-mounts and I lose the user. I tried logging the user and receive null.
Hopefully I explained this well enough... here is the code:
App component:
import React from 'react';
import List from './List';
import ListForm from './ListForm';
import * as helpers from '../helpers';
class App extends React.Component{
state = {
currentUser: '',
}
componentDidMount() {
helpers.fetchUser()
.then(data => {
this.setState({
currentUser: data
});
});
}
onSubmit = (id, item) => {
helpers.addItem(id, item);
}
render() {
return (
<div className="container">
<List
currentUser={this.state.currentUser}
onSubmit={this.onSubmit}
/>
</div>
);
}
};
export default App;
Helpers:
import axios from 'axios';
export const fetchUser = async () => {
const resp = await axios.get('/api/current_user');
return resp.data;
}
export const addItem = (id, newItem) => {
axios.post("/api/" + id + "/addItem", newItem);
}

Resources