User disconnected for a few seconds when reloaded using AuthContext [duplicate] - node.js

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>
);
}

Related

In here, the data successfully Stored message shows. But data is not showing in the mongo DB database. What is the trouble with it?

In here, the data successfully Stored message shows. But data is not showing in the mongo DB database. What is the trouble with it?
I'm creating a simple cruds application using React.js and node.js, connecting using Axios. I use DB as mongo DB. This is the data insertion react.js file.
const onSubmit = () => {
const data = {
name: name,
age: age,
city: cityArray,
};
axios.post("http://localhost:5000/student", data).then((res) => {
if (res.data.success) {
alert("Added Succes");
window.location = "/";
setAge("");
setCity("");
setName("");
}
});
};
What is the Exact Error?
Did you Implement the PreventDefault function?
if not, please use the following code,
e.prventDefault();
in the onSubmit function ,do the following changes
const onSubmit = (e) => {
e.prventDefault();
const data = {
name: name,
age: age,
city: cityArray,
};
Please refer this you can get clear idea
import React from 'react';
import {
BrowserRouter,
Routes,
Route,
} from "react-router-dom";
import AddPosts from './components/AddPosts.jsx';
import Posts from './components/Posts.jsx';
import UpdatePosts from './components/UpdatePosts.jsx';
import UserRegister from './components/UserRegister';
import UserLogin from './components/UserLogin';
export function App() {
return(
<div>
<BrowserRouter>
<Routes>
<Route path="/" element={ <UserLogin/>}/>
<Route path="/user/register" element={ <UserRegister/>}/>
<Route path="/add" element={<AddPosts/>}></Route>
<Route path="/view" element={<Posts/>}></Route>
<Route path="/update/:id" element={<UpdatePosts/>}></Route>
</Routes>
</BrowserRouter>
</div>
);
}

How to forward page from react js frontend to node js backend using proxy

I created a simple react app that has two pages ("/", "/show-stocks") using Routes to switch between the two. I created a node/express js backend that has an end point called "/show-stocks/btc", I setted the proxy in the package.json to "proxy": "http://localhost:4000/", Now I want to be able to forward to the backend the page "/show-stocks/btc" (the preset end point).
I tried this solution but it didn't worked. It does take the user to /show-stocks/btc but in the localhost:3000 and sends an error that says that this path does not exsist.
I saw on the internet that if the page does not exists its automatically checks if the proxy has it.
(React listening on port 3000, Node listening on port 4000)
Does someone know how can I implement this?
App.js
import React from "react";
import Navbar from "./components/Navbar";
import "./App.css";
import Home from "./components/pages/Home";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import ShowStocks from "./components/pages/ShowStocks";
function App() {
return (
<>
<Router>
<Navbar />
<Routes>
<Route path="/" exact element={<Home />} />
<Route path="/show-stocks" element={<ShowStocks />} />
</Routes>
</Router>
</>
);
}
export default App;
ShowStocks.js:
(incase user goes to "localhost:3000/show-stocks")
import React from "react";
import "../../App.css";
import GetStocks from "../../GetStocks";
export default function ShowStocks() {
return (
<>
<GetStocks />
</>
);
}
GetStocks:
import React, { Component } from "react";
import axios from "react-axios";
import { Link } from "react-router-dom";
export default class GetStocks extends Component {
constructor() {
super();
this.state = {
BTCprice: "Noy Yet Gotten",
};
}
getBTC = () => {
// here I am trying to get response from the proxy /show-stocks/btc end point.
axios.get("/btc").then((response) => {
console.log(response.data.price);
this.setState({
BTCprice: response.data.price,
});
});
};
render() {
return (
<div>
<h1>BTC price: {this.state.BTCprice}</h1>
<Link to="/show-stocks/btc" className="btn-mobile">
<button className={`btn--test`} onClick={this.getBTC}>
Get price
</button>
</Link>
</div>
);
}
}
I fixed it
the problem where at GetStocks.js
this is the new code
import React, { Component } from "react";
import axios from "axios";
export default class GetStocks extends Component {
constructor() {
super();
this.state = {
BTCprice: "Noy Yet Gotten",
};
}
getBTC = () => {
console.log("jey");
axios.get("/show-stocks/btc").then((response) => {
console.log(response.data.price);
this.setState({
BTCprice: response.data.price,
});
});
};
render() {
return (
<div>
<h1>BTC price: {this.state.BTCprice}</h1>
<button onClick={this.getBTC}>Get price</button>
</div>
);
}
}

Why is react admin login page only shown for one second?

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;

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'));

Resources