I've following issue with my ionic react app.
After initial launch the Login page appears as expected.
After successful login, this.state.login.status turns true and the Home component gets loaded (seeing log output of this component). But the screen only shows a blank page.
After reloading the page (with successful login data; I'm saving this in localStorage), the Home component appears as expected.
This behaviour is the same on every device (Chrome, Android, Ios). And the console doesn't show any error message.
I only saw one difference between Android and Chrome. If I logout with chrome, the Login page appears and I can relog (without the blank page). So the issue is only for the first state change.
If I'm using my android app (built with Capacitor), the blank page appears on every state change and I have to restart the app, after every login/logout.
Does anyone have an idea what could cause this behaviour? I already searched for similar problems, but nothing of them worked. For instance changing the homepage to '.' in package.json.
App.tsx:
import React from 'react';
import { Redirect, Route } from 'react-router-dom';
import { IonApp, IonRouterOutlet } from '#ionic/react';
import { IonReactRouter } from '#ionic/react-router';
import Home from './pages/Home';
import Login from './pages/Login';
import Xxx from './util/Xxx';
/* Core CSS required for Ionic components to work properly */
import '#ionic/react/css/core.css';
/* Basic CSS for apps built with Ionic */
import '#ionic/react/css/normalize.css';
import '#ionic/react/css/structure.css';
import '#ionic/react/css/typography.css';
/* Optional CSS utils that can be commented out */
import '#ionic/react/css/padding.css';
import '#ionic/react/css/float-elements.css';
import '#ionic/react/css/text-alignment.css';
import '#ionic/react/css/text-transformation.css';
import '#ionic/react/css/flex-utils.css';
import '#ionic/react/css/display.css';
/* Theme variables */
import './theme/variables.css';
interface AppProps {};
interface AppState {
isSignedIn: boolean,
user: string
};
class App extends React.Component<AppProps, AppState> {
constructor(props: AppProps) {
super(props);
const login = Xxx.isSignedIn();
this.state = {
isSignedIn: login.status,
user: login.user
};
this.checkLogin = this.checkLogin.bind(this);
}
checkLogin() {
const login = Xxx.isSignedIn();
this.setState({
isSignedIn: login.status,
user: login.user
});
}
render() {
return (
<IonApp>
<IonReactRouter>
<IonRouterOutlet>
<Route
path="/home"
exact={true}
render={
props => {
return this.state.isSignedIn ?
<Home {...props} user={this.state.user} checkLogin={this.checkLogin}/> :
<Login checkLogin={this.checkLogin}/>;
}
}
/>
<Route exact path="/" render={() => <Redirect to="/home" />} />
</IonRouterOutlet>
</IonReactRouter>
</IonApp>
);
}
};
export default App;
Could you try this ?
I think it's because the virtual dom do not change...
if (!this.state.isSignedIn) {
return <Login checkLogin={this.checkLogin}/>
}
return (
<IonApp>
<IonReactRouter>
<IonRouterOutlet>
<Route
path="/home"
exact={true}
render={
props => <Home {...props} user={this.state.user} checkLogin={this.checkLogin}/>
}
}
/>
<Route exact path="/" render={() => <Redirect to="/home" />} />
</IonRouterOutlet>
</IonReactRouter>
</IonApp>
);
look at using React.Context to create an context for managing the application state, then you dont need to be passing the state all over the place with properties
const App: React.FC = () => {
const { authCtx } = React.useContext(AppContext);
return (
<IonApp>
{authCtx.authenticated ? (
<IonReactRouter>
<IonSplitPane contentId="main">
<Menu />
<IonRouterOutlet id="main">
<Route path="/page/:name" component={Page} exact />
<Redirect from="/" to="/page/Inbox" exact />
</IonRouterOutlet>
</IonSplitPane>
}
</IonReactRouter>
) : (
<IonReactRouter>
<Route path="/auth/login" component={LoginPage} exact />
<Route
path="/auth/create-account"
component={CreateAccountPage}
exact
/>
<Redirect from="/" to="/auth/login" exact />
</IonReactRouter>
)}
</IonApp>
);
};
IN index.ts
import { AuthProvider } from "./helpers/my-context";
ReactDOM.render(
<AuthProvider>
<App />
</AuthProvider>,
document.getElementById("root")
);
In my-context.ts
import React from "react";
// create the context
export const Context = React.createContext<any>(undefined);
// create the context provider, we are using use state to ensure that
// we get reactive values from the context...
export const AuthProvider: React.FC = ({ children }) => {
// the reactive values
const [authCtx, setAuthCtx] = React.useState({
authenticated: null,
user: null,
});
// the store object
let state = {
authCtx,
setAuthCtx,
};
// wrap the application in the provider with the initialized context
return <Context.Provider value={state}>{children}</Context.Provider>;
};
export default Context;
Related
I want to redirect the unauthenticated user to /login. I have a property in my context called loggedIn, when it is false I want to make a redirect to HomePage i.e "/".
I just got confused due to the route structure. I'm seeing something like a protected route everywhere.
import React, { useContext, useEffect } from "react";
import { Redirect, Route } from "react-router-dom";
import { IonApp, IonRouterOutlet, IonSplitPane } from "#ionic/react";
import { IonReactRouter } from "#ionic/react-router";
import { setupConfig } from "#ionic/react";
import { Plugins, Capacitor } from "#capacitor/core";
import { useHistory } from "react-router-dom";
import Home from "./pages/Home/Home";
/* Core CSS required for Ionic components to work properly */
import "#ionic/react/css/core.css";
/* Basic CSS for apps built with Ionic */
import "#ionic/react/css/normalize.css";
import "#ionic/react/css/structure.css";
import "#ionic/react/css/typography.css";
/* Optional CSS utils that can be commented out */
import "#ionic/react/css/padding.css";
import "#ionic/react/css/float-elements.css";
import "#ionic/react/css/text-alignment.css";
import "#ionic/react/css/text-transformation.css";
import "#ionic/react/css/flex-utils.css";
import "#ionic/react/css/display.css";
import "../src/utils/spacing.css";
/* Theme variables */
import "./theme/variables.css";
/* Components */
import Dashboard from "./pages/Dashboard/Dashboard";
import SideMenu from "./components/SideMenu/SideMenu";
import LoginPage from "./pages/Login/Login";
import SignupPage from "./pages/Signup/Signup";
import Create from "./pages/Create/Create";
import Edit from "./pages/Edit/Edit";
import { AuthContext, AuthProvider } from "./providers/context/AuthContext";
const App: React.FC = () => {
const history = useHistory();
return (
<IonApp>
<AuthProvider>
<IonReactRouter>
<IonSplitPane contentId="main">
<SideMenu />
<IonRouterOutlet id="main">
<Route path="/dashboard/:name" component={Dashboard} exact />
<Route path="/dashboard/Edit/:id" component={Edit} exact />
// if !user navigate to login component
<Route path="/create" component={Create} exact />
<Route path="/signup" component={SignupPage} exact />
<Route path="/" component={Home} exact />
<Redirect from="/dashboard" to="/dashboard/Home" exact />
</IonRouterOutlet>
</IonSplitPane>
</IonReactRouter>
</AuthProvider>
</IonApp>
);
};
export default App;
Might be useful for anyone: is solved it by using the Private Route and pubic Route trick
I Created a component called PrivateRoute.tsx and added the following:
import React, { useContext } from "react";
import { Route, Redirect } from "react-router-dom";
import { EnrolleeContext } from "../providers/context/EnrolleeContext";
const PrivateRoute = ({ component: Component, ...rest }: any) => {
const { loggedIn } = useContext(EnrolleeContext);
return (
// Show the component only when the user is logged in
// Otherwise, redirect the user to /signin page
<Route
{...rest}
render={(props) =>
loggedIn ? <Component {...props} /> : <Redirect to="/" />
}
/>
);
};
export default PrivateRoute;
Also created a Public Route, added restricted to be able to restrict Components if restricted == true, meaning the Route is Private, if false, otherwise.
import React, { useContext } from "react";
import { Route, Redirect } from "react-router-dom";
import { EnrolleeContext } from "../providers/context/EnrolleeContext";
const PublicRoute = ({ component: Component, restricted, ...rest }: any) => {
const { loggedIn } = useContext(EnrolleeContext);
return (
// restricted = false meaning public route
// restricted = true meaning restricted route
<Route
{...rest}
render={(props) =>
loggedIn && restricted ? (
<Redirect to="/dashboard/Home" />
) : (
<Component {...props} />
)
}
/>
);
};
export default PublicRoute;
Updated App.tsx
<PublicRoute restricted={false} component={Home} path="/" exact />
<PublicRoute
restricted={true}
component={LoginPage}
path="/login"
exact
/>
<PrivateRoute
component={Dashboard}
path="/dashboard/:name"
exact
/>
<PrivateRoute component={Create} path="/create" exact />
<PublicRoute
restricted={false}
component={ForgotPassword}
path="/forgot-password"
exact
/>
Other Routes like forgot password is open to the public`, while Create is Private, so it is at your own discretion.
My react component should work as follows:
Check a global variable for error and if there is error redirect to home and unset the global error variable.
Otherwise it should render a which will use current page's location to redirect to correct location.
How should I go about doing this. The only way which somehow works is if I do conditional render and set global variable in render. Is there any better way?
In this example I'm using context api to share the state across all routes available.
For question No. 1 - use protected route. (Much simpler & neat)
predeclared path & component on <ProtectedRoute path="/anyPath" component={anyComponent}/>
if you want to have flexibility on setting the path yourself, then go to methods No. 2 below
in App.js:-
import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import { ErrorState } from "./contexts/ErrorState";
// import ProtectedRoute from "./comps/routes/ProtectedRoute";
import ProtectedRouteCustom from "./comps/routes/ProtectedRouteCustom";
import Home from "./comps/Home";
import Demo from "./comps/Demo";
import Dashboard from "./comps/Dashboard";
import "./style.css";
export default function App() {
return (
<ErrorState>
<Router>
<NavBar />
<Switch>
<Route exact path="/" component={Home} />
<Demo exact path="/demo" component={Demo} />
{/*} <ProtectedRoute exact path="/demo/dashboard" component={Dashboard} /> */}
<ProtectedRouteCustom path="/demo" />
<Route path="*" component={() => "Not Found"} />
</Switch>
</Router>
</ErrorState>
);
}
ProtectedRoute.js (emphasis more on Route render):-
import React from "react";
import { Redirect, Route } from "react-router-dom";
import { useError } from "../../contexts/ErrorState";
const ProtectedRoute = ({ component: Component, ...rest }) => {
const [errorState, errorDispatch] = useError();
const { error } = errorState;
return (
<Route
{...rest}
render={props => {
// you can use props.location.pathname to redirect user to the route path you have specified in App.js (see in console.log)
console.log(props);
// render Dashboard component if 'error' === false
if (!error) return <Component {...props} />;
// redirect to homepage if error === false
else
return (
<Redirect
to={{
// specify the path to redirect (if condition 'error' === true)
pathname: "/",
state: { from: props.location }
}}
/>
);
}}
/>
);
};
export default ProtectedRoute;
For question No. 2 - you can build your own custom protected route
path can be specified or set it yourself in ProtectedRouteCustom
App.js:-
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import { ErrorState } from "./contexts/ErrorState";
// import ProtectedRoute from "./comps/routes/ProtectedRoute";
import ProtectedRouteCustom from "./comps/routes/ProtectedRouteCustom";
import Home from "./comps/Home";
import Demo from "./comps/Demo";
import Dashboard from "./comps/Dashboard";
import "./style.css";
export default function App() {
return (
<ErrorState>
<Router>
<NavBar />
<Switch>
<Route exact path="/" component={Home} />
<Demo exact path="/demo" component={Demo} />
{/*} <ProtectedRoute exact path="/demo/dashboard" component={Dashboard} /> */}
<ProtectedRouteCustom path="/demo" />
<Route path="*" component={() => "Not Found"} />
</Switch>
</Router>
</ErrorState>
);
}
custom protected route:- (emphasis more on Redirect rather than Route render)
import React from "react";
import { Redirect, Route } from "react-router-dom";
import { useError } from "../../contexts/ErrorState";
import Dashboard from "../Dashboard";
const ProtectedRouteCustom = ({ path }) => {
const [errorState, errorDispatch] = useError();
const { error } = errorState;
return (
<>
{error ? (
<Redirect to="/" />
) : (
<>
<Redirect from={path} to={path + "/dashboard"} />
<Route to={path + "/dashboard"} component={Dashboard} />
</>
)}
</>
);
};
export default ProtectedRouteCustom;
You can see the sandbox here for full working code.
Guidelines on how to use the sandbox code
Environment for No. 1:-
uncomment/enable:
import ProtectedRoute from "./comps/routes/ProtectedRoute";
<ProtectedRoute exact path="/demo/dashboard" component={Dashboard} />
comment/disable:
import ProtectedRouteCondition from "./comps/routes/ProtectedRouteCondition";
<ProtectedRouteCondition path="/demo" />
Environment for No. 2: run as it is
Generally all coder using your methods. If you checking React in Facebook Group. You can find correct answer ı think.
Im trying to use React Routing V6 for my project.
Currently im struggeling to make the authentication and routing itself to work.
the idea of my code is:
Not authenticated user:
redirect to /login with my login component. (only login component)
Authenticated user:
Load the gameComponent component, and the rest of links inside of gamecomponent, will load inside gameComponents div class named middleContentHolder
examples:
authenticated user:
visits url /crime -> loads gamecomponent, and within gamecomponent it loads the crime component.
visits url /test -> loads gamecomponent , and within gamecomponent it loads the SideBarRight component.
not authenticated user:
vitits url /crime -> not authenticated -> redirects to /login -> loads loginmodule only.
please note that in gamecomponent component, i do have links that will load within gamecomponent.
app.js will either load the gamecomponent, or redirect user to login if not auth.
app.js:
import React from 'react';
import logo from './logo.svg';
import './App.css';
import GameComponent from './gameComponent.jsx';
import { BrowserRouter as Router } from 'react-router-dom';
import { Routes, Route, Navigate, Outlet } from 'react-router-dom';
import Crime from './components/game/crime.jsx';
import PrivateRoute from './PrivateRoute';
import Login from './components/login/login.jsx';
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<GameComponent />}>
<PrivateRoute isAuth={true} path="crime" component={Crime} redirectTo='/login'/>
</Route>
</Routes>
</Router>
);
}
export default App;
Gamecomponent:
import React, {Component} from 'react';
//import Component from 'react-dom';
import SideBarRight from './components/game/sideBarRight.jsx';
import SideBarLeft from './components/game/sideBarLeft.jsx';
import Crime from './components/game/crime.jsx';
import Login from './components/login/login.jsx';
import './gameComponent.css';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import { BrowserRouter} from "react-router-dom";
class GameComponent extends Component{
constructor() {
super();
this.state = {
userData: {
user: {cash:0, bank:0, weapon:'', username: 'test', locationname: 'Bankok',
defence: 0},
rankbar: {rankpercent: 50, rank: 'Mafia'},
}
}
}
render() {
return (
<div className="main">
<div className="sidebar left">
<SideBarLeft/>
</div>
<div className="middleContentHolder">
<Route path="/" element={<Crime />} />
<Route path="/test" element={<Crime />} />
<Route path="/crime" element={<Crime />} />
<Route path="/test" element={<SideBarRight UserData={this.state.userData} />} />
<div className="col-8">
<div className="content">
<div className="header"><span>Test...</span></div>
</div>
</div>
</div>
<div className="sidebar right">
<SideBarRight UserData={this.state.userData}/>
</div>
</div>
);
}
}
export default GameComponent;
PrivateRoute:(auth is just a dummy atm)
import React from 'react';
import PropTypes from 'prop-types';
import { Route, Navigate } from 'react-router-dom';
import { useNavigate } from "react-router-dom";
import Login from './components/login/login.jsx';
import GameComponent from './gameComponent.jsx';
const PrivateRoute = ({ component: Component, redirectTo, isAuth, path, ...props }) => {
isAuth = false;
if(!isAuth) {
return <Navigate to={redirectTo} />;
}
return <Route path={path} element={<Component />} />
};
export default PrivateRoute;
update:
orginal auth was:in privateroute:
isAuth = isAuth;
one example of non-working code that would show what i want:
<Route path="/login" element={}>
<PrivateRoute isAuth={true} path="/" component={GameComponent} redirectTo='/login'>
rest of routes exist in gamecomponent..
</PrivateRoute>
If you only want GameComponent to load if use is authenticated, you will need to change your App component like this:
function App() {
return (
<Router>
<Routes>
<Route path="/login" element={<LoginComponent />} />
<PrivateRoute isAuth={true} path="/" component={GameComponent} redirectTo='/login'/>
</Routes>
</Router>
);
}
Here we are essentially putting a switch so that we can navigate to /login when there is no authentication. <Routes> is vital here, because it will only render the component that matches the exact path.
With the official release of React Router V6, the other answer is no longer valid. It will throw an error since <PrivateRoute /> isn't a <Route />.
The proper way to do it is to refactor your <PrivateRoute /> component like so...
import { Navigate, useLocation } from "react-router-dom"
const PrivateRoute = (props: { children: React.ReactNode }): JSX.Element => {
const { children } = props
// Up to you specific app to decide how to get this value
const isLoggedIn: boolean = localStorage.getItem('logged_user') !== null;
const location = useLocation()
return isLoggedIn ? (
<>{children}</>
) : (
<Navigate
replace={true}
to="/login"
state={{ from: `${location.pathname}${location.search}` }}
/>
)
}
Then whichever file you setup your routes in, you would do...
<Routes>
<Route path="/PRIVATE" element={<PrivateRoute> <PrivatePage /> </PrivateRoute>}/>
<Route path="/profile" element={<PrivateRoute> <ProfilePage /> </PrivateRoute>}/>
<Route path="/login" element={<LoginPage />}/>
<Route path="/" element={<HomePage />}/>
</Routes>
This is the proper way of doing it in V6 since only a <Route /> can be nested in a <Routes />. Then your authenticated logic gets moved into the element prop.
As an added bonus, the state={{ from: `${location.pathname}${location.search}` }} in PrivateRoute allows you to get the URL of the page they tried to enter, but was denied. This is passed to your login page, where you can redirect them back to the URL after they authenticate.
Solution for newer version
Create custom middleware <AuthGuard> and use as wrapper
<PrivateRoute> not vaild for newer version as child of <Routes>
Error "A <Route> is only ever to be used as the child of <Routes> element"
App.js
import React from 'react'
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import AuthGuard from "./Routes/AuthGuard";
function App() {
return (
<div className='App'>
<Router>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/contact' element={<Contact />} />
<Route path='/guest-page' element={<AuthGuard isGuest={true}><h1>Guest Page</h1></AuthGuard>} />
<Route path='/protected-page' element={<AuthGuard requireToken={true}><h1>ProtectedPage</h1></AuthGuard>} />
</Routes>
</Router>
</div>
);
}
export default App;
AuthGuard.js
import { Route, useNavigate } from "react-router-dom";
import { useLayoutEffect } from "react";
const ProtectedRoute = ({requireToken, guest, children, ...rest}) => {
console.log(requireToken, guest, children, rest);
const navigate = useNavigate();
const hasToken = false;
let hasAccess = (!requireToken || (requireToken && hasToken));
let navigateTo = '/?login-rquired=true';
if(guest) {
hasAccess = !hasToken;
navigateTo = '/?guest=true';
console.log("Guest > hasAccess: " + hasAccess)
}
if(requireToken){
console.log("requireToken", requireToken)
}
useLayoutEffect(()=>{
if (!hasAccess) {
console.log("Not allowed");
navigate(navigateTo);
}
},[])
return (
<>{ hasAccess ? children : "Login required" }</>
)
}
export default ProtectedRoute;
I am trying to redirect from my context following a failed update of the state from the a cookie.
import React, { createContext, Component } from 'react';
import { withRouter } from "react-router-dom";
import Cookies from 'universal-cookie';
export const MyContext = createContext();
const cookies = new Cookies();
class MyProvider extends Component {
componentDidMount() {
this.setStateFromCookie();
}
setStateFromCookie = () => {
try {
this.setState({ data: cookies.get('my-cookie')['data'] });
} catch(error) {
console.log(error);
this.props.history.push('/');
}
return
};
render() {
return (
<MyContext.Provider value={{...this.state}}>
{this.props.children}
</MyContext.Provider>
);
}
}
export default withRouter(MyProvider);
I am using a withRouter hook to this.props.history.push('/'), becuase the context is wrapping the router
class MyApp extends Component {
render() {
return (
<BrowserRouter>
<MyProvider>
<div className="MyApp">
<Router>
<Route exact path='/' component={Index} />
<Route exact path='/dashboard' component={Dashboard} />
</Router>
</div>
</MyProvider>
</BrowserRouter>
);
}
}
export default MyApp;
The problem is that the redirect to the home page following the error, but the home page isn't rendering.. I still see the dashboard page.
Any idea what is going on and how to fix this
The issue is that you have a nested Router wrapping your Routes. You need to remove that and then everything will work fine
<BrowserRouter>
<MyProvider>
<div className="MyApp">
<Route exact path='/' component={Index} />
<Route exact path='/dashboard' component={Dashboard} />
</div>
</MyProvider>
</BrowserRouter>
When you use a nested Router, and try to navigate from Provider, the history used by Provider is being provided by BrowserRouter and hence it isn't able to communicate to the Routes whcih are dependent on the inner <Router> component for history.
Using a single router wrapping your components solves this issue
There is one need for url authentication:
import React from "react";
import { connect } from "react-redux";
import { Switch, Route, Redirect } from "react-router-dom";
...
const IndexRouter = ({ loggedIn }) => (
<Switch>
<Route
path="/"
render={() => (loggedIn ? <Redirect to="/dashboard" /> : <Login />)}
/>
<Route exact path="/dashboard" component={DashboardRouter} />
<Route exact path="/stock" component={StockRouter} />
</Switch>
);
export default connect(
state => ({
loggedIn: state.persist.loggedIn
}),
{}
)(IndexRouter);
The code means if I have not logged in, all of url are required from client will redirect to Login component. Other than that it will route to DashboardRouter.
The StockRouter is another route related with DashboardRouter.
The problem is that if I logged in. All the unspecific url (except /dashboard, /stock) I manually typed showing the /dashboard url without anything. The specific url such as /stock can show the component StockRouter directly.
You would need to write a PrivateRoute wrapper around your Route and change the order of Routes in IndexRouter, so that the Route with path / is matched at the last otherwise all routes will match / first and will not render correctly
const PrivateRoute = ({component: Component, loggedIn, ...rest }) => {
if(!loggedIn) {
return <Redirect to="/login" />
}
return <Route {...rest} component={Component}/>
}
}
}
const IndexRouter = ({ loggedIn }) => (
<Switch>
<PrivateRoute exact path="/dashboard" component={DashboardRouter} />
<PrivateRoute exact path="/stock" component={StockRouter} />
<Redirect to="/dashboard" />
</Switch>
);
For more details, check Performing Authentication on Routes with react-router-v4
Just create a history component like this :
import React from "react";
import {withRouter} from "react-router";
let globalHistory = null;
class HistoryComponent extends React.Component {
componentWillMount() {
const {history} = this.props;
globalHistory = history;
}
componentWillReceiveProps(nextProps) {
globalHistory = nextProps.history;
}
render() {
return null;
}
}
export const GlobalHistory = withRouter(HistoryComponent);
export default function gotoRoute(route) {
return globalHistory.push(route);
}
And then import into your component:
import gotoRoute from "../../history";
gotoRoute({
pathname: "/your_url_here",
state: {
id: this.state.id
}
});
And in index.js
import {GlobalHistory} from "./history";
ReactDOM.render((
<Provider store={store}>
<BrowserRouter >
<div>
<GlobalHistory/>
<App/>
</div>
</BrowserRouter>
</Provider>
), document.getElementById('root'));