Access isAuthenticated prop using Redux - reactjs

I think this is a relatively easy problem, however, I'm getting a little confused using Redux. Basically I'm trying to create a PrivateRoute Component and I can't figure out how to access the isAuthenticated prop. Help is appreciated.
UPDATE
Path: App.js
class App extends Component {
componentDidMount() {
store.dispatch(loadUser());
}
render() {
return (
<Provider store={store}>
<Router>
<Header />
<Switch>
<Route exact path="/login">
<ErrorBoundary>
<LoginPage />
</ErrorBoundary>
</Route>
<PrivateRoute path="/purchase_order">
<p>Test</p>
</PrivateRoute>
</Switch>
</Router>
</Provider>
);
}
}
Path: PrivateRoute
const PrivateRoute = ({ children, ...rest }) => (
<Route
{...rest}
render={({ location }) =>
this.props.isAuthenticated ? (
children
) : (
<Redirect
to={{
pathname: '/login',
state: {
from: location
}
}}
/>
)
}
/>
);
const mapStateToProps = (state) => ({
auth: state.auth
});
export default connect(mapStateToProps)(PrivateRoute);

You have done great, you also have structured the functional component very well for PrivateRoute, but there is a mistake while accessing the props, and using as this.props.isAuthenticated, first thing this.props is used only and only in class-based React component or you can use it as (props.isAuthenticated assumed that you have passed isAutheticated prop in PrivateRoute React functional component).
Points you have done-
You have connected your global state to this PrivateRoute functional component.
You have structurally defined your PrivateRoute functional component.
Redux state -
state: {
auth: {userId: 'ob2-51-hw-s124' // any random string based on your API},
otherReducer: {...}
}
Points to be done-
import connect from the react-redux module.
import Route, Redirect from the react-router-dom module.
Final Code
AppRouter.js
// module import
import LandingPage from '...';
import NotFoundPage from '...';
import DashBoardPage from '...';
import LoginPage from '...';
// React functional component that can be imported in App.js
const AppRouter = () => (
<Router history={history}>
<div>
<Switch>
<PublicRoute path="/" exact={true}>
<LandingPage />
</PublicRoute>
<PrivateRoute path="/dashboard">
<DashboardPage />
</PrivateRoute>
<PrivateRoute path="/login">
<LoginPage />
</PrivateRoute>
<PublicRoute path="/help" >
<HelpPage />
</PublicRoute>
<Route>
<NotFoundPage />
</Route>
</Switch>
</div>
</Router>
);
export default AppRouter;
PrivateRoute.js
// module import
import React form 'react';
import { connect } from 'react-redux';
import { Route, Redirect } from 'react-router-dom';
// React functional PrivateRoute component
export const PrivateRoute = ({
auth,
children,
...rest
}) => {
const isAuthenticated = !!auth.userId; // converting auth.userId(typeof string) to false/true(typeof boolean)
return (
<Route {...rest} component={(props) => (
isAuthenticated ? (
children
) : (
<Redirect
to={{
pathname: '/login',
state: {
from: props.location
}
}}
/>
)
)}
/>
);
}
// redux connect
const mapStateToProps = (state) => ({
auth: state.auth
});
export default connect(mapStateToProps)(PrivateRoute);

Your mapStateToProps function shows that you put the state.auth value into the auth prop
You are using a function component, so there is no this.
So, assuming that isAuthenticated is a property of auth, then:
rest.auth.isAuthenticated otherwise: rest.auth
You can also destructure the auth from the props and get direct access to it. Since you don't need to pass it into the route.
const PrivateRoute = ({ children, auth, ...rest }) => (
<Route
{...rest}
render={({ location }) =>
auth.isAuthenticated ? (
children
) : (
<Redirect
to={{
pathname: '/login',
state: {
from: location
}
}}
/>
)
}
/>
);
const mapStateToProps = (state) => ({
auth: state.auth
});
export default connect(mapStateToProps)(PrivateRoute);

Related

How to use React HOC for authenticated routing?

I'm trying to add private routing to my app, but I get this error:
"Invariant failed: You should not use <Route> outside a <Router>"
Here is my code:
App.js
class App extends Component {
render() {
return (
<BrowserRouter>
<Route render={({ history }) => (
<div className="App">
<Navbar history={history} />
<Switch>
<Auth path="/" component={HomePage} currUser={this.props.currUser} />
<Route path="/login" render={(props) => (<LoginPage {...props} login={this.props.login} />)} />
</Switch>
</div>
)} />
</BrowserRouter>
);
}
}
const Auth = ({ component: Component, ...rest }) => {
const {currUser} = rest;
return (
<Route {...rest} render=
{props =>
currUser ?
(<Component {...props} currUser={currUser.name} />) :
(<Redirect to={{ pathname: "/login", state: currUser }} />)
} />
)
}
const mapStateToProps = (state) => {
return {
currUser: state.auth.currUser
}
}
const mapDispatchToProps = {
...authActions,
}
export default connect(mapStateToProps, mapDispatchToProps)(Auth);
What am i doing wrong?
And how do I pass props from Redux state to the components in this method?
Thanks!
You wrapped the Auth component with the App component, so your file should export the App. When you export only the Auth, the <Route> tag in Auth is outside the router tag.
export default connect(mapStateToProps, mapDispatchToProps)(App);

React router render not getting props

My application requires users to authenticate before they can access specific routes. For this, I am trying to follow react-router's auth-workflow example.
routes/index.js
// Private Route
import PrivateRoute from "../containers/PrivateRoute";
export default (
<Switch>
<Route
path="/login"
component={Login}
exact={true}/>
<Route
path="/changePassword"
component={ChangePassword}
exact={true}/>
<PrivateRoute
path="/groups"
component={ListGroups}
exact={true}/>
<Route
path="/verify"
component={VerificationCode}
exact={true}/>
<Route component={NoMatch}/>
</Switch>
);
containers/PrivateRoute
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import PrivateRoute from "../routes/PrivateRoute";
const mapStateToProps = (state) => {
return {
jwtTokens: state.auth.jwtTokens,
email: state.auth.email,
isAuthenticated: state.auth.isAuthenticated
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({}, dispatch);
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(PrivateRoute);
routes/PrivateRoute.js
export default function PrivateRoute({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={(props) => {
return (props.isAuthenticated) ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: {
from: props.location
}
}}
/>
);
}}
/>
);
}
When debugging in Chrome DevTools, when entering the render function, I am not getting the full values as I am in rest. Screenshots below represent my debugging in Chrome and the values I can see at each stage:
In the image above, I can see the values; jwtTokens, isAuthenticated, and email. To my knowledge from following examples, using the spread operator on rest is how I pass the values to render as a param. The next screenshot shows this is not the case:
I am able to simply use the rest param to access the properties but shouldn't I be just as easily be able to access the same values from props as well?
The render callback of a Route that you are using to render Component does not forward props received by the Route from outside. It only contains the location, match and params of the route. What you probably wanted to do is to spread rest into the <Component /> rather then the Route as a Route will not use any of those nor does it expect those props:
export default function PrivateRoute({ component: Component, ...rest }) {
return (
<Route
render={(props) => {
return (rest.isAuthenticated) ? (
<Component {...props} {...rest} /> {/* add rest here instead */}
) : (
<Redirect
to={{
pathname: "/login",
state: {
from: props.location
}
}}
/>
);
}}
/>
);
}
You also need to check rest.isAuthenticated instead of props.isAuthenticated for the same reason. isAuthenticated will not be in the props passed to the render callback.
Also see in the react-router docs about Route props about which props will be passed to the render callback:
Route props
All three render methods will be passed the same three route props
match
location
history

How to use Redux inside a React Router stateless middleware?

When some user try to access any page of my application, a React Router middleware is called to check if the user is logged in. The problem is that to access the Redux store I need to use store.getState() and if I use store.listen(...) I receive an error ('component' doesn't exist).
const PrivateRoute = ({ component: Component, ...rest }) => { // Receive component and path
return (
<Route { ...rest } render={(props) => {
return store.getState().login.token.status === 200
? <Component { ...props } />
: <Redirect to="/login" />
}}
/>
)
}
// store.subscribe(PrivateRoute) // Error when some page is requested
export default PrivateRoute;
I know that to listen changes on this function I need to pass a component, but I think it's not possible because it's a middleware. Do I really need to listen for changes in this case, if yes, how can I do this (how to mock this type of thing) ?
Obs : Example <PrivateRoute path="/private" component={() => <h1>Private</h1>}/>
Try this.
const PrivateRoute = ({ component: Component, hasLogin, ...rest }) => (
<Route
{...rest}
render={props => (
hasLogin ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: '/login'
}}
/>
)
)}
/>
)
export default connect(state => ({
hasLogin: state.login.hasLogin
}))(PrivateRoute)
In the switch call like this
<PrivateRoute exact path='/' component={some page} />
Using Redux
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, auth: auth, ...rest }) => (
<Route
{...rest}
render={props =>
auth.isAuthenticated === true ? (
<Component {...props} />
) : (
<Redirect to="/login" />
)
}
/>
);
PrivateRoute.propTypes = {
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(mapStateToProps)(PrivateRoute);
After login have you dispatch an action to store the the login values in store.
if yes check that.
and i think you can store the login token in localStorage read about WebStorage and easily get it back using store.get('keyname').

How can I rewrite this React Component as a pure function and get an error/warning free implementation with React Router?

I've been struggling with rewriting this react component to a pure function per my eslint error recommendations. My goal is to also get this to work with react router without it throwing a warning/error.
Here is the class component:
class PrivateRouteContainer extends React.Component {
render() {
const {
isAuthenticated,
component: Component,
...props
} = this.props
return (
<Route
{...props}
render={props =>
isAuthenticated
? <Component {...props} />
: (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}} />
)
}
/>
)
}
}
source: https://github.com/ReactTraining/react-router/blob/master/packages/react-router-redux/examples/AuthExample.js
Here is my eslint error:
Here is my attempt:
const PrivateRouteContainer = (props) => {
const {
isAuthenticated,
isLoggingIn,
} = props;
return (
<Route
{...props}
render={() =>
(isAuthenticated || isLoggingIn
? <Component {...props} />
: (
<Redirect to={{
pathname: '/login',
state: { from: props.location },
}}
/>
))
}
/>
);
};
This doesn't seem to work as I'm getting the error:
Warning: You should not use and in the same route; will be ignored
Here are my route definitions if it helps:
import React from 'react';
import PropTypes from 'prop-types';
import { Route, Switch } from 'react-router';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { ConnectedRouter } from 'react-router-redux';
import PrivateRoute from './PrivateRoute';
import Login from '../containers/LoginPage';
import Home from '../containers/HomePage';
import List from '../containers/ListPage';
import Edit from '../containers/EditPage';
import { logout } from '../redux/auth/oauth/actions';
const ConnectedSwitch = connect(state => ({ location: state.routerReducer.location }))(Switch);
const mapStateToProps = state => ({
isLoggingIn: state.authReducer.isLoggingIn,
isAuthenticated: state.authReducer.isAuthenticated,
error: state.authReducer.error,
});
const Logout = connect(mapStateToProps)(({ dispatch }) => {
dispatch(logout());
return <Redirect to="/" />;
});
const Routes = props =>
(
<ConnectedRouter history={props.history}>
<ConnectedSwitch>
<Route path="/" exact component={connect(mapStateToProps)(Home)} />
<PrivateRoute path="/list" exact component={List} />
<PrivateRoute path="/edit" component={Edit} />
<Route path="/login" exact component={connect(mapStateToProps)(Login)} />
<Route path="/logout" exact component={Logout} />
</ConnectedSwitch>
</ConnectedRouter>
);
Routes.propTypes = {
history: PropTypes.shape({}).isRequired,
};
export default connect(mapStateToProps)(Routes);
This is just a typo as I think you understood "this" is not defined in a stateless component :
const {
isAuthenticated,
isLoggingIn,
} = props; // <-- remove this. here
const PrivateRouteContainer = (props) => (
<Route
{...props}
render={() =>
(props.isAuthenticated || props.isLoggingIn
? <Component {...props} />
: (
<Redirect to={{
pathname: '/login',
state: { from: props.location },
}}
/>
))
}
/>
)
Instead of passing authentication as props, have you considered creating a utils file, such as auth.js that easily returns if the user is validated or not?
If we tangle the authentication conditions within component disposition we're adding an additional layer which we're going to have to care about, which is having to care about the parent component's life cycle and how it evaluates to authentication. This also means you're passing to the <Route /> component, some props that it doesn't care or doesn't need to care about, and that could be problematic as well
To make things clearer, also try using the object destructuring property instead of declaring const inside of your component, as well as the rest operator. Pass to route only what route needs, and to <Component />, only what component needs. That could be causing errors,
The final result is something like this:
import { isAuthenticated } from 'utils/auth';
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/login' }} />
)
)}/>
);
I'm not sure if this is the best approach or not, but it seems to be working...
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Route } from 'react-router';
import { Redirect } from 'react-router-dom';
const RedirectToLogin = () => (
<Redirect to={{ pathname: '/login' }} />
);
const PrivateRouteContainer = props => (
<Route
{...props}
component={props.isAuthenticated || props.isLoggingIn ?
props.component :
RedirectToLogin}
/>
);
PrivateRouteContainer.propTypes = {
isAuthenticated: PropTypes.bool.isRequired,
isLoggingIn: PropTypes.bool.isRequired,
component: PropTypes.func.isRequired,
};
const PrivateRoute = connect(state => ({
isAuthenticated: state.authReducer.isAuthenticated,
isLoggingIn: state.authReducer.isLoggingIn,
}))(PrivateRouteContainer);
export default PrivateRoute;

React router private routes / redirect not working

I have slightly adjusted the React Router example for the private routes to play nice with Redux, but no components are rendered when Linking or Redirecting to other 'pages'. The example can be found here:
https://reacttraining.com/react-router/web/example/auth-workflow
Their PrivateRoute component looks like this:
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
fakeAuth.isAuthenticated ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}}/>
)
)}/>
)
But, because I have incorporated it in a Redux application, I had to adjust the PrivateRoute a little so I can access the redux store, as well as the route Props:
const PrivateRouteComponent = (props) => (
<Route {...props.routeProps} render={() => (
props.logged_in ? (
<div>{props.children}</div>
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}} /> )
)} />
);
const mapStateToProps = (state, ownProps) => {
return {
logged_in: state.auth.logged_in,
location: ownProps.path,
routeProps: {
exact: ownProps.exact,
path: ownProps.path
}
};
};
const PrivateRoute = connect(mapStateToProps, null)(PrivateRouteComponent);
export default PrivateRoute
Whenever I'm not logged in and hit a PrivateRoute, I'm correctly redirected to /login. However, after for instance logging in and using a <Redirect .../>, or clicking any <Link ...> to a PrivateRoute, the URI updates, but the view doesn't. It stays on the same component.
What am I doing wrong?
Just to complete the picture, in the app's index.js there is some irrelevant stuff, and the routes are set up like this:
ReactDOM.render(
<Provider store={store}>
<App>
<Router>
<div>
<PrivateRoute exact path="/"><Home /></PrivateRoute>
// ... other private routes
<Route path="/login" component={Login} />
</div>
</Router>
</App>
</Provider>,
document.getElementById('root')
);
You need to wrap your Route with <Switch> tag
ReactDOM.render(
<Provider store={store}>
<App>
<Router>
<div>
<Switch>
<PrivateRoute exact path="/"><Home /></PrivateRoute>
// ... other private routes
<Route path="/login" component={Login} />
</Switch>
</div>
</Router>
</App>
</Provider>,
document.getElementById('root'));
Set the private route to not be pure:
export default connect(mapStateToProps, null, null, {
pure: false,
})(PrivateRoute);
This will let the Component re-render.
Please see: react-router-4-x-privateroute-not-working-after-connecting-to-redux.
Just had same problem, I solved it by making my App redux container and passing isAuthenticated as a prop to PrivateRoute
Here it is, I hope it helps
const App = (props) => {
return (
<Provider store={store}>
<Router>
<div>
<PrivateRoute path="/secured" component={Secured} isAuthenticated={props.isAuthenticated} />
</div>
</Router>
</Provider>
);
};
const mapStateToProps = state => ({
isAuthenticated: state.isAuthenticated
});
export default connect(mapStateToProps)(App);
Then in my PrivateRoute
const PrivateRoute = ({ component: Component, isAuthenticated, ...rest}) => (
<Route
{...rest}
render={props => (
isAuthenticated
? (
<Component {...props} />
)
: (<Redirect to={{ pathname: '/login', state: { from: props.location} }} />)
)}
/>
);
export default PrivateRoute;
I managed to get this working using the rest parameter to access the data from mapStateToProps:
const PrivateRoute = ({component: Component, ...rest}) => {
const {isAuthenticated} = rest;
return (
<Route {...rest} render={props => (
isAuthenticated ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login',
state: {from: props.location}
}}/>
)
)}
/>
);
};
PrivateRoute.propTypes = {
isAuthenticated: PropTypes.bool.isRequired,
};
function mapStateToProps(state) {
return {
isAuthenticated: state.user.isAuthenticated,
};
}
export default connect(mapStateToProps)(PrivateRoute);
Well, I think the answer to this question should really be more detailed, so here I am, after 4 hours of digging.
When you wrap your component with connect(), React Redux implements shouldComponentUpdate (sCU if you search answers on github issues) on it and do shallow comparison on props (it goes throug each key in the props object and check if values are identical with '==='). What it means in practice is that your component is considered Pure. It will now change only when its props change and only then! This is the key message here.
Second key message, React router works with context to pass the location, match and history objects from Router to Route component. It doesn't use props.
Now let's see in practice what happen, because even knowing that, I find it still pretty tricky:
Case 1:
There is 3 key to your props after connecting: path, component, and auth (given by connect). So, in fact, your wrapped component will NOT re-render at all on route changes because it doesn't care. When route changes, your props don't change and it will not update.
Case 3:
Now there is 4 keys to your props after connecting: path, component, auth and anyprop. The trick here is that anyprop is an object that is created each time the component is called. So whenever your component is called this comparison is made: {a:1} === {a:1}, which (you can try) gives you false, so your component now updates every single time. Note however that your component still doesn't care about the route, its children do.
Case 2:
Now that's the mystery here, because i guess you call this line in your App file, and there should be no reference to "auth" in there, and you should have an error (at least that's what i am guetting). My guess is that "auth" in your App file references an object defined there.
Now what should we do ?
I see two options here:
Tell React Redux that your component is not pure, this will remove the sCU injection and your component will now correctly update.
connect(mapStateToProps, null, null, {
pure: false
})(PrivateRoute)
Use WithRouter(), which will results in injecting the location, match and history object to your component as props. Now, I don't know the internals, but i suspect React router doesn't mutate those objects so each time the route change, its props change (sCU returns true) as well and your component correctly updates. The side effect of this is that your React tree is now polluted with a lot of WithRouter and Route stuff...
Reference to the github issue: Dealing with Update Blocking
You can see here that withRouter is intended as a quickfix but not a recommended solution. using pure:false is not mentionned so I don't know how good this fix could be.
I found a 3rd solution, though it's unclear to me if it is really a better solution than withRouter, using Higher Order Component. You connect your Higher-order Component to the Redux store, and now your route doesn't give a damn about what it renders, the HOC deals with it.
import Notlogged from "./Notlogged";
function Isloggedhoc({ wrap: Component, islogged, ...props }) {
return islogged ? <Component {...props} /> : <Notlogged {...props} />;
}
const mapState = (state, ownprops) => ({
islogged: state.logged,
...ownprops
});
export default connect(mapState)(Isloggedhoc);
in your App.js
<Route path="/PrivateRoute" render={props => <Isloadedhoc wrap={Mycomponent} />} />
You could even make a curried function to shorten it a bit:
function isLogged(component) {
return function(props) {
return <Isloadedhoc wrap={component} {...props} />;
};
}
Using it like that:
<Route path="/PrivateRoute" render={isLogged(Mycomponent)} />
I have struggled with this issue as well, and here is my solution.
Instead of passing isAuthenticated to every < PrivateRoute> component, you just need to get isAuthenticated from state in < PrivateRoute> itself.
import React from 'react';
import {Route, Redirect, withRouter} from 'react-router-dom';
import {connect} from 'react-redux';
// isAuthenticated is passed as prop here
const PrivateRoute = ({component: Component, isAuthenticated , ...rest}) => {
return <Route
{...rest}
render={
props => {
return isAuthenticated ?
(
<Component {...props} />
)
:
(
<Redirect
to={{
pathname: "/login",
state: {from: props.location}
}}
/>
)
}
}
/>
};
const mapStateToProps = state => (
{
// isAuthenticated value is get from here
isAuthenticated : state.auth.isAuthenticated
}
);
export default withRouter(connect(
mapStateToProps, null, null, {pure: false}
)(PrivateRoute));
Typescript
If you are looking for a solution for Typescript, then I made it work this way,
const PrivateRoute = ({ component: Component, ...rest }: any) => (
<Route
{...rest}
render={props =>
localStorage.getItem("authToken") ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
<Router>
<Switch>
<PrivateRoute exact path="/home" component={Home} />
<Route path="/login" component={Login} />
</Switch>
</Router>
Just in case you want to go by creating a class then something like this,
class PrivateRoute extends Route {
render() {
if (localStorage.getItem("authToken")) {
return <Route {...this.props} />
} else {
return <Redirect
to={{
pathname: "/login",
state: { from: this.props.location }
}}
/>
}
}
}
According to react-router documentation you may just wrap your connect function with withRouter:
// before
export default connect(mapStateToProps)(Something)
// after
import { withRouter } from 'react-router-dom'
export default withRouter(connect(mapStateToProps)(Something))
This worked for me and my views started to be updated along with routes in this case.
I have the similar issue like #Rein. In my case, PrivateRoute looks almost same to the original version but only connected to Redux and used it instead of fakeAuth in the original example.
const PrivateRoute = ({ component: Component, auth, ...rest }) => (
<Route
{...rest}
render={props =>
auth.isAuthenticated
? <Component {...props} />
: <Redirect to={{ pathname: "/login" }} />}
/>
);
PrivateRoute.propTypes = {
auth: PropTypes.object.isRequired,
component: PropTypes.func.isRequired
}
const mapStateToProps = (state, ownProps) => {
return {
auth: state.auth
}
};
export default connect(mapStateToProps)(PrivateRoute);
Usage and result:-
NOT working but expecting to work
<PrivateRoute path="/member" component={MemberPage} />
working but NOT desired to used like this
<PrivateRoute path="/member" component={MemberPage} auth={auth} />
working. JUST to work but NOT desired to used at all. An understanding from this point is that, if you connect original PrivateRoute to Redux, you need to pass some additional props (any prop)to make PrivateRoute working otherwise it does not work. Anyone, please give some hint on this behavior. This is my main concern. As a New Question at
<PrivateRoute path="/member" component={MemberPage} anyprop={{a:1}} />

Resources