I am currently in the process of creating a react webpage, using starlette as my web server framework that hooks up my database and provides the API. To improve code separation and unneeded loading of files I divided my page into two separately built react pages. One for the login page before verification, and one for the main page once verification has been completed and the user has a valid token. The problem with this is that both react web pages send GET requests as an example to: /static/js/2.91da4595.chunk.js.
I am wondering if it is possible for me to change where react will send requests to when looking for static files. So for example my login page will look to /otherstatic/js/2.91da4595.chunk.js instead.
There might be a more elegant way to reach the point I want to, so feel free to suegest a different method. Let me know if any further explanation or code is needed, and I can add it to this post.
You may need to do code-splitting. Read here for further information.
Code-splitting your app can help you “lazy-load” just the things that are currently needed by the user, which can dramatically improve the performance of your app. While you haven’t reduced the overall amount of code in your app, you’ve avoided loading code that the user may never need, and reduced the amount of code needed during the initial load.
I assume you used react-router-dom, so here's a simple implementation:
import React, { Suspense } from 'react';
import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom';
const HomePage = React.lazy(() => import('./HomePage'));
const LoginPage = React.lazy(() => import('./LoginPage'));
function MyApp() {
const [auth, setAuth] = React.useState({
isLoading: true,
isAuthenticated: false,
data: null,
})
React.useEffect(() => {
const checkAuth = () => {
// call setAuth here
}
checkAuth()
}, [])
const MyRoute = ({ component: Component, authorized: false, ...rest }) => (
<Route
{...rest}
render={props => {
if (auth.isLoading) return null
if (authorized) { // Home page access
return auth.isAuthenticated
? <Component {...prop} />
: <Redirect to="/login" />
} else { // Login page access
return !auth.isAuthenticated
? <Component {...prop} />
: <Redirect to="/" />
}
}}
/>
)
return (
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<MyRoute path="/login" component={LoginPage} authorized={false} />
<MyRoute path="/" component={HomePage} authorized={true} />
</Switch>
</Suspense>
</BrowserRouter>
);
}
Related
my site is built using MERN stack, when I refresh a page it first shows the main page and then the page where the user is. How to fix this issue?
For example:
if I refresh (/profile) page then for a meanwhile it shows (/) then it redirects to (/profile). I want if I refresh (/profile) it should be on the same page.
import { Route, Redirect } from 'react-router-dom';
const PrivateRoute = ({ component: Component, authed, ...rest }) => {
return (
<Route
{...rest}
render={(props) => authed === true
? <Component {...props} />
: <Redirect to={{ pathname: '/', state: { from: props.location } }} />}
/>
)
}
export default PrivateRoute;
Router code:
const App = () => {
const user = useSelector((state) => state?.auth);
return (
<>
<BrowserRouter>
<Container maxWidth="lg">
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" exact component={About} />
<Route path="/terms" exact component={Terms} />
<PrivateRoute authed={user?.authenticated} path='/profile' component={Profile} />
</Switch>
</Container>
</BrowserRouter>
</>
)
}
export default App;
How to fix so that user stays on the same page if its refreshed? The issue is on the pages where authentication is required.
When first authenticated the user, store the credentials(the info that you evaluate to see if the user is authenticated. Tokens etc.) in the localStorage. Of course you have to create necessary states too.
Then with useEffect hook on every render set the credential state from localStorage.
function YourComponentOrContext(){
const[credentials, setCredentials] = useState(null);
function yourLoginFunction(){
// Get credentials from backend response as response
setCredentials(response);
localStorage.setItem("credentials", response);
}
useEffect(() => {
let storedCredentials = localStorage.getItem("credentials");
if(!storedCredentials) return;
setCredentials(storedCredentials);
});
}
I guess on mounting (=first render) your user variable is empty. Then something asynchronous happen and you receive a new value for it, which leads to new evaluation of {user?.authenticated} resulting in true and causing a redirect to your /profile page.
I must say I'm not familiar with Redux (I see useSelector in your code, so I assume you are using a Redux store), but if you want to avoid such behaviour you need to retrieve the right user value on mounting OR only render route components when you've got it later.
Am trying to implement the ProtectedRoute. But when i pass authentication, meaning am authenticated rightly from the auth API, get my token, i get pushed to the main page without a problem.
However, when i type in the browser the URL manually to the protected route, i get redirected to the Login page.
Am storing the isAuthenticated flag in a redux store using redux-toolkit. Check out my code below
ProtectedRoute.jsx
import React, { useState, useEffect } from 'react';
import { Route, Redirect } from "react-router-dom";
import { useSelector } from 'react-redux'
const ProtectedRoute = ( { component: Component, ...rest }) => {
const isLoggedIn = useSelector(state => state.auth.isAuthenticated);
const [ isAuthed, setAuthed ] = useState(false);
useEffect(() => {
setAuthed(isLoggedIn);
}, [isLoggedIn]);
return (
<Route
{...rest}
render={
props => (
isAuthed === true
? <Component {...props} />
: <Redirect to={{ pathname: '/', state: { from: props.location} }}/>
)
}
/>
)
}
export default ProtectedRoute
The composition of the App.js are as follows
<Router>
<Switch>
<Route exact path="/" component={SignIn} />
<ProtectedRoute exact path="/main" component={ Dashboard } />
<Route path="*">
<div>404 Not found</div>
</Route>
</Switch>
</Router>
I tried passing this login as a prop from the App.js to the ProtectedRoute but still i cant get the result i want.
What i realized though is that there's double rendering. On the first time, reading the isLoggedIn variable produces a false result and therefore forces a redirect.
Typing URL manually or copying the URL to another tab destroys the OLD state. This happens when we try to get our state from redux Store using useSelector.
I solved it by using backend code and providing expiry time for each user logged in and than linked that user with store's State.
To check if user's time is expired or not i used npm package Decode and this small code.
const token = user?.token; //this user is srored in localstorage
// JWT..
if (token) {
const decodedToken = decode(token);
if (decodedToken.exp * 1000 < new Date().getTime()) logout(i); //here is what you
// need at frontend
}
setUser(JSON.parse(localStorage.getItem("profile")));
I'm trying to build an app with Ionic React. I have an issue with the router.
App.tsx
<IonRouterOutlet>
<Route path="/stats" render={() => <Stats/>} exact={true} />
<Route path="/orders" component={Orders} exact={true} />
<Route path="/" render={() => <Redirect to="/stats" />} exact={true} /></IonRouterOutlet>
In my component Stats, I use useEffect to load the data from the API
useEffect(() => {
OrdersService.getOrders().then(resultat => {
setDataOrders(resultat);
});
return function cleanup() {
// Clean the data
}
}, []);
If I go to my component Orders and go back to stats, react don't reload the data from the API.
Do you have any solution to force the reload ?
Thanks in advance.
Ionic works differently to how you might expect, the routes are rendered even if they have not been entered yet. And when you navigate to a new route, the cleanup function of useEffect does not get called. This is by design so routes are already rendered and good to go when you navigate between pages.
What you want to use is the useIonViewWillEnter hook which will be called when the route is about to be entered.
import React, { useState } from "react";
import { useIonViewWillEnter } from "#ionic/react";
export const DataOrdersView = () => {
const [dataOrders, setDataOrders] = useState([]);
useIonViewWillEnter(() => {
OrdersService.getOrders().then((resultat) => {
setDataOrders(resultat);
});
});
return <div>Your View</div>;
};
You can read more here,
https://ionicframework.com/docs/react/lifecycle
In Gatsby, how would I restrict routes programmatically? Using react-router, I see it's possible to do a <Redirect> with <Route>, but how would this be implemented in Gatsby? To do something like this...
<Route exact path="/" render={() => (
loggedIn ? (
<Redirect to="/dashboard"/>
) : (
<PublicHomePage/>
)
)}/>
Where would I put this file in Gatsby? Would I put it in src/pages or elsewhere?
Edited, asking for additional clarification...
I'm able to get this work per the advice from #Nenu and the Gatsby docs. The docs gave a non-asynchronous example, so I had to tweak it for interacting with a remote server like this...
async handleSubmit(event) {
event.preventDefault()
await handleLogin(this.state)
.then(response => _this.setState({isLoggedIn: isLoggedIn()}))
.catch(err => { console.log(err) });
}
Also, I am able to use the <PrivateRoute /> with this.
Unfortunately though, when I render using...
render() {
if (isLoggedIn()) {
return <Redirect to={{ pathname: `/app/profile` }} />
}
return (
<View title="Log In">
<Form
handleUpdate={e => this.handleUpdate(e)}
handleSubmit={e => this.handleSubmit(e)}
/>
</View>
)
}
...while I do indeed <Redirect to={{ pathname:/app/profile}} />, I notice that a split-second before I redirect, the form fields are emptied and only after that do I get redirected to /app/profile (from /app/login). Also, if I type in an incorrect password, my whole form is re-rendered (re-rendering <View /> again). This would be a bad user-experience because they they'd have to re-enter all of their info from scratch, plus I'd be unable to add styling for invalid inputs, etc. I'm wondering if there is a better way to do this with Gatsby.
Or, would I have to build form functionality more from scratch (i.e., using Redux, Router, etc more directly) rather than depending upon Gatsby's higher level of abstraction?
Gatsby uses react-router under the hood, therefore you can define your client-only routes with it.
There is, as always with gatsby, a very nice example in the github repo:
https://github.com/gatsbyjs/gatsby/tree/master/examples/simple-auth
And the doc about it:
https://www.gatsbyjs.org/docs/building-apps-with-gatsby/#client-only-routes--user-authentication
To sum up, this is what is done:
1) Create a PrivateRoute component in /src/components
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
!isLoggedIn() ? (
// If we’re not logged in, redirect to the login page.
<Redirect to={{ pathname: `/app/login` }} />
) : (
<Component {...props} />
)
}
/>
);
2) Define routes in your wanted prefix "client-only" path
Let suppose you want to restrict access of the /app/:path section of your website, then in /src/pages/app.js:
const App = () => (
<div>
<PrivateRoute path="/app/profile" component={Home} />
<PrivateRoute path="/app/details" component={Details} />
<Route path="/app/login" component={Login} />
</div>
);
These routes will exist on the client only and will not correspond to index.html files in an app’s built assets. If you wish people to visit client routes directly, you’ll need to setup your server to handle these correctly. (source)
3) White-list the client-routes in gatsby-node.js
exports.onCreatePage = async ({ page, boundActionCreators }) => {
const { createPage } = boundActionCreators
// page.matchPath is a special key that's used for matching pages
// only on the client.
if (page.path.match(/^\/app/)) {
page.matchPath = `/app/:path`
// Update the page.
createPage(page)
}
}
I'm trying to create a protected route that will redirect to /login when user is not authorized using Found Router for Relay Modern based off the example given by React Router:
const PrivateRoute = ({ component: Component, ...rest }) => {
return (<Route {...rest} render={props => {
if (!props) return
if (Component && props && props.viewer) {
return (<Component {...props} />)
} else {
throw new RedirectException('/login')
}
}}
/>)
}
I'm replacing the fakeAuth with real login logic, but the rest is the same. The Route just doesn't render.
Found Router seems to be light on examples around this specific problem. Any ideas?
I ended up splitting out my login and authenticated routes:
export default makeRouteConfig(
<Route>
<LoginRoute exact path='/login' Component={Login} query={LoginQuery} />
<PrivateRoute path='/' Component={App} query={AppQuery}>
</Route>
)
And extending Route for LoginRoute like this:
export class LoginRoute extends Route {
render({ Component, props }) {
if (!props) return undefined
if (Component && props && props.viewer) {
throw new RedirectException('/')
}
return (<Component {...props} />)
}
}
And the PrivateRoute looks much the same, but with different redirects in the case that there is no viewer.
It works pretty well.
Jimmy Jia, the creator of the project had some other suggestions that I didn't end up using but may be useful to any future readers. See my closed issue here.