React Router V4 protected private route with Redux-persist and React-snapshot - javascript

I'm implementing private route like so using React Router Route Component:
function PrivateRoute({component: Component, authed, emailVerified, ...rest}) {
return (
<Route
{...rest}
render={props =>
authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/', state: {from: props.location}}} />}/>
)
}
Expected Behavior:
authed is persisted through page refresh through using redux-persist
So on page refresh or reload, if authed prop is true then router should render <Component /> without ever going to path "/"
Actual Behavior which is the Problem:
With authed === true (persisted)
reloading the page or refreshing it leads to the following actions taking place(checked redux devtools)
the action:
"##router/LOCATION_CHANGE" runs and takes it to the correct secure route but then "##router/LOCATION_CHANGE" runs again and it redirects to "/" for a moment and finally
"##router/LOCATION_CHANGE" runs again and directs route back to the secure path, even though authed === true through all this in the redux devtools
Then: My guess was that this error has something to with my main App Component rendering before redux-persist has time to re-hydrate the Redux store.
So I tried doing the following:
I tried delaying my main App component render until my store is re-hydrated using redux-persist like so:
render() {
const {authed, authedId, location, rehydrationComplete} = this.props
return (
<div>
{ rehydrationComplete
? <MainContainer>
<Switch key={location.key} location={location}>
<Route exact={true} path='/' component={HomeContainer} />
<Route render={() => <h2> Oops. Page not found. </h2>} />
</Switch>
</MainContainer>
: <div>...Loading </div> }
</div>
)
}
This effectively fixes the issue above of the path changing when "##router/LOCATION_CHANGE" action runs(only Changes the path keys), However this leads to another Issue with React-snapshot Now: all the static generated html files from react-snapshot Now contain only ...Loading. I tried to set snapshotDelay of 8200 in the react-snapshot options but that didnt solve the issue.
Then:
I tried the following to delay React-snapshot call so that it renders html after the store has been rehydrated:
import {render as snapshotRender} from 'react-snapshot'
import {ConnectedRouter} from 'react-router-redux'
async function init() {
const store = await configureStore()
snapshotRender(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById('root')
)
registerServiceWorker()
}
init()
But now i get the error: that 'render' from react-snapshot was never called. Did you replace the call to ReactDOM.render()?
I know this is a loaded question, but I want to effectively use these 3 libs(React-Router V4, Redux-persist, React-snapshot) together to serve protected routes without the mentioned errors.

I have something similar to you. Here I use React-Router V4 and a persist-like library.
Your router/routes doesn't need to be aware of the persist. They should rely on your redux's store. The persist should rehydrate your store with all the data.
I didn't see where you are using the PrivateRoute component in your example. Where is it?

Related

Why would I want to nest my React Routes?

I am having a hard time understanding why I would want to nest my react routes through components. In the following example you have two components spread across multiple files.
File 1:
<Route path='/topics' component={Topics} />
File 2:
const Topics = ({ match }) => (
<div>
<h2>Topics</h2>
<Link to={`${match.url}/exampleTopicId`}>
Example topic
</Link>
<Route path={`${match.path}/:topicId`} component={Topic}/>
</div>
)
I am currently building a CRM for my company, where instead I am doing this:
routes/index.js
const routes = [
...
{ path: '/app/analytics/competitors', component: Competitors, meta: { title: 'Competitor Data', description: '' } },
{ path: '/app/analytics/competitors/:id', component: CompetitorById, meta: { title: 'Competitor Report', description: '' } }]
export default routes
App.js:
<Provider {...RootStore}>
<Router history={RootStore.routerStore.history}>
<Switch>
{routes.map((route) => {
return (
<Route
key={route.path}
path={route.path}
component={route.component}
exact={true}
meta={route.meta}
/>
);
}
})}
</Switch>
</Router>
</Provider>
When you have dozens of routes, I find this to be much clearer to read, implement and understand what is going on with my router.
It is more similar to the way I have seen routes implemented in other frameworks, such as VueJS's Vue StoreFront.
Is there anything wrong with what I am doing here, and if not, why do people nest their routes?
Thanks :)
Edit:
To further extend, I am using the routes as following for anything that required authorisation:
return (
<AppRoute
key={route.path}
path={route.path}
component={route.component}
exact={true}
meta={route.meta}
/>
);
This named route are being declared like so:
const AppRoute = ({ ...props }) =>
RootStore.userStore.hasSession ? (
<Theme>
<Route {...props} />
</Theme>
) : (
<Redirect to={"/login"} />
);
Within theme we have shared components that are used between every page, such as the navbar and header. This prevents re-rendering, so in essence each route is a container rather than a whole page.
One short but important reason would be re-rendering.
If you've got one route rendering under /foo and one under /foo/:id, then React would not need to re-render the foo route compinent, even when you switch from one id to another. If you don't nest them, everything has to be re-rendered again.
This has even more benefits when you want to add some simple rules, e.g. only allow users to enter /foo when they're authenticated. You can add this rule to your /foo route component. If a user is authenticated, the content is rendered, as well as all sub-routes become available. Without nesting, you would need to implement it in every route again.

How to make component not re-render when navigating pages?

In my application there is a main home page that gets loaded and there is another page (Page2.js) I can navigate to via the navbar I have implemented. The problem is that when I go to Page2.js and then go back to the main page, it will re-render all of the components on the main page. I am wondering if there is a way to check the last pathname and say if it is equal to a certain value then all of the components should stay the same?
I have tried using the shouldComponentUpdate method in the navbar (since I do not want it to change) and it is pretty faulty in that it would not read prevProps.location as a value but undefined:
shouldComponentUpdate = (prevProps) => {
if (this.props.location !== prevProps.location) {
return false;
}
}
App.js holds the code for my routing in the application:
// imports here
class App extends Component {
render() {
const {location} = this.props;
return (
<Switch >
<DefaultLayoutRoute exact path="/">
<Redirect to={HomePage}/>
</DefaultLayoutRoute>
<Route exact path={SIGNOUT} render={() => {
auth0Client.signOut();
window.location.href = homeRedirect;
return null
}}>
</Route>
<Route exact path={LOGIN_SUCCESS} component={Callback}/>
<DefaultLayoutRoute exact path={HomePage} location={location.pathname} component={HomePage}
pageName="Office Production"/>
<DefaultLayoutRoute
exact
path={AGENTRANKING}
component={AgentRankings}
pageName="Agents"
/>
<DefaultLayoutRoute exact path={SETTINGS} component={Settings}/>
</Switch>
);
}
}
export default withRouter(App);
Expected Result: To be able to navigate to Page2.js and then back to the main page with all of the components rendered the way it was unless I manually refresh
Actual Results: I go back to the main page and all the components start to re-render.

React Router re-render route instead of re-render component

I'm working on an app that is using React Router and I noticed that when my Redux store changes state that the router is re-rendering the component the current route refers to rather than re-rendering the route itself.
To illustrate the problem; I have implemented a PrivateRoute that checks if a user is currently logged in. In it's most basic form it looks something like this:
const PrivateRoute = ({component: Component, ...rest}) => {
return <Route {...rest} render={(props) => {
const state = store.getState()
if (state.isAuthenticated) {
return <Component {...props}/>
}
else {
return <Redirect to={{pathname: '/login'}}/
}
}}/>
})
This works great since I can now say something like this:
<PrivateRoute path="/" component={HomePage}/>
However, I noticed that when the state of isAuthenticated changes that React Router is calling the render method on the HomePage component rather than that it re-renders the route. This means that the application will only do the authentication check when a user goes from some page to the home page but once on the home page, the check is no longer performed.
The only work around I have at the moment is to move the authentication check into the render function of the component (which is obviously not where it belongs).
How can I make React Router re-render the route rather than re-render the component the route refers to when the state changes?
I managed to solve the problem by using a Higher Order Component rather than implementing the authentication check in the route.
function withEnsureAuthentication(WrappedComponent) {
return class extends React.Component {
render() {
if (this.props.store.isAuthenticated === false) {
return <Redirect to={{pathname: '/login'}}/>
}
return <WrappedComponent {...this.props}/>
}
}
}
You can now use a normal Route but apply the withEnsureAuthentication to the component:
const HomePage = withEnsureAuthentication(Home)
<Route path="/home" component={HomePage}/>

react-router-redux redirect to absolute url

I'm migrating a react application and I'm trying to split it. Basically, I would like to redirect some client-side react routes to absolute urls (or relative, but at least go with a server roundtrip, where reverse proxying is done)
Note that
react-router 3.0.0
react-router-redux 4.0.7
The app have these urls
http://myhost/ => homepage
http://myhost/someroute1 => a first route
http://myhost/someroute2 => a second route
http://myhost/someroute3 => a third route
Everything is inside react right now.
Routing looks like this :
<Provider store={store}>
<Router history={history}>
<Route path="/" component={Root}>
<IndexRoute component={Home} />
<Route path="/someroute1" component={Route1} />
<Route path="/someroute2" component={Route2} />
<Route path="/someroute3" component={Route3} />
</Route>
</Router>
</Provider>
The goal is to redirect, say, routes "/" and "/someroute2" to static urls (server urls). As so :
http://myhost/ => http://anotherhost/
http://myhost/someroute1 => keep react routing
http://myhost/someroute2 => http://anotherhost/anotherroute5
http://myhost/someroute3 => keep react routing
The question is simple : is is possible to replace, in a clean way, a react router route with an absolute url ?
I heard about Redirect and IndexRedirect components, but I can't figure how to use it properly, and, due to a lack of react / react-router, I can't figure if there would be any dangerous side-effects (in history for example).
Use Route's render prop instead of component. That way, you can specify a function to be called instead of a component to be instantiated. In the function, perform the navigation the old-fashioned way, using window.location.href:
<Route
path="/someroute2"
render={() => {
window.location.href = "http://anotherhost/anotherroute5";
return null;
}}
/>
Partially based on #brub answer, I've found a solution using a dumb component.
import React, { Component } from 'react'
export default class MyRedirectRoute extends Component {
componentDidMount() {
window.location.href = //my url here
}
render() {
return null
}
}
That I pass like this
<Route path="/someroute3" component={MyRedirectRoute} />
Though, I'm still not aware of a few things :
Is this a recommended solution ?
Are there any history side-effect ?
Is there any better (more react-router) solution than window.location.href ? I tried this.context.history without any success...
I'm waiting for feedback / better solution before accepting my own answer
You probably don't need React Router for this. The creator of React Router suggests using the <a> tag.
I haven't tried it but syntactically you could do it like this:
<Route
path="/someroute2"
render={() => <Redirect to="http://anotherhost/anotherroute5" />}
/>

React Router 4 authentification and redirection

I'm trying to implement an authentification system to my app with React Router.
But i'm having trouble to find a correct way to do this.
export default class App extends React.Component {
constructor(props){
super(props);
this.state = {
logged : false
}
}
isUserConnected(){
// Contact server to know if user is connected or not
}
render() {
return (
<Router>
<Switch>
<Route exact path="/" component={Homepage} />
<Route path="/privatePart" component={Application} />
<Route path="/login" component={LoginRedirection} />
<Route component={NotFound} />
</Switch>
</Router>
)
}
}
In the LoginRedirection component, there is a login form that make a POST to connect the user. But how this component can set logged to true in the App component ?
Maybe I should learn redux ? It may be simplier to implement authentification with it ?
Redux could be a solution, but you can do this with React and react-router.
Using react-router, you can use the property onEnter. For example, you can make the privatePart URL accessible only to logged users with something like:
<Route path="/privatePart" component={Application} onEnter={requireAuth}/>
requireAuth should be a function that checks if the user has logged in or not. I think that using App's state isn't the best way to do this, since that information will be lost if the web is reloaded. You could store the login token or just a flag in the localStorage when the POST returns OK in the Login component. Then, redirect the user to privatePart and he will only be able to access it if requireAuth returns true (checking the localStorage). It could be something like:
if (localStorage.getItem("logged") === null) {
return false;
} else {
return true;
}
where username is the key you would add to the localStorage when the login is successful.

Categories

Resources