how to access redux store from react component for authentication - javascript

I'm maintaining a react redux app and trying to get authentication to one of the routes in the app, namely /dashboard . I want to pass in a boolean state from redux store to a prop named authed but struggling... As currently, I just pass in true value as a fake value.
import React from 'react'
import {
HashRouter,
Route,
Link,
Switch,
Redirect
} from 'react-router-dom'
// components that are main pages
import Home from './containers/Home'
import Login from './containers/Login'
import Signup from './containers/Signup'
import NotFound from './containers/NotFound'
import Dashboard from './containers/Dashboard'
import IntersectionForm from './containers/IntersectionForm'
import IntersectionDetail from './containers/IntersectionDetail'
import { connect } from 'react-redux'
const PrivateRoute = ({component: Component, authed, ...rest}) => {
return (
<Route
{...rest}
render={(props) => authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/', state: {from: props.location}}}/>}
/>
)
}
function mapStateToProps (state) {
return state
}
const PrivateRouteContainer = connect(mapStateToProps)(PrivateRoute)
const Routes = (history) => {
return (
<HashRouter history={history}>
<switch>
<Route exact path="/" component={Login}/>
<Route exact path="/signup" component={Signup}/>
<PrivateRouteContainer authed={true} path='/dashboard' component={Dashboard}/>
</switch>
</HashRouter>
)
}
export default Routes

Make a call to your auth end-point (POST) in auth_actions from your componentDidMount function.
dispatch an action once you get the response within actioncreator.
in authReducer - for example: isAuthenticated:true/false and return the payload.
access that value by making your react component connected and within
mapStatetoprops of the component and you can access this boolean
value - using this.props.authValue.

function mapStateToProps (state) {
return state
}
by doing this your component will receive props with all your states across reducers.
for example, if you have:
import { combineReducers } from 'redux'
import todos from './todos'
import counter from './counter'
export default combineReducers({
todos,
counter
})
Then your PrivateRoute will get todos and counter props.
That's why its better if your mapStateToProps grabs just the prop it needs.
function mapStateToProps (state) {
return {
authed: state.nameOfReducer.isAuthed, // or whetever is the value you need to know if user is authorized
}
}
If you, however, don't combineReducers and you have just one reducer in your app then:
function mapStateToProps (state) {
return {
authed: state.isAuthed,
}
}

Related

Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop - ProtectedRoutes Component

I am attempting to create a ProtectedRoutes component, however, I seem to have created an infinite loop somewhere that I can't seem to figure out. I'm a beginner.
It should check if there is a cookie stored, and if so, go to the component. If not, it should navigate back to the main page.
ProtectedRoutes.js
import React, { Component, useState } from "react";
import { Route, Navigate } from "react-router-dom";
import Cookies from "universal-cookie";
const cookies = new Cookies();
export default function ProtectedRoutes({component: Component, ...rest}) {
const [auth, setAuth] = useState(false);
//get cookie from browser if logged in
const token = cookies.get("TOKEN");
if (token) {
setAuth(true);
};
return auth ? <Component /> : <Navigate to="/" />
}
App.js
import { Container, Col, Row } from "react-bootstrap";
import "./App.css";
import Register from "./Register";
import Login from "./Login";
import { Routes, Route } from "react-router-dom";
import Account from "./Account";
import FreeComponent from "./FreeComponent";
import AuthComponent from "./AuthComponent";
import Private from "./ProtectedRoutes";
import ProtectedRoutes from "./ProtectedRoutes";
function App() {
return (
<Container>
<Row>
<Col className="text-center">
<h1 className="header">React Authentication Tutorial</h1>
<section id="navigation">
Home
Free Component
Auth Component
</section>
</Col>
</Row>
{/* Routes */ }
<Routes>
<Route exact path="/" element={ <Account /> } />
<Route exact path="/free" element={ <FreeComponent /> } />
<Route path="/auth" element={<ProtectedRoutes component={AuthComponent} />} />
</Routes>
</Container>
);
}
export default App;
AuthComponent.js
import React from 'react';
export default function AuthComponent() {
return (
<div>
<h1 className="text-center">Auth Component</h1>
</div>
);
}
Yow problem Is heaw.
export default function ProtectedRoutes({component: Component, ...rest}) {
const [auth, setAuth] = useState(false);
//get cookie from browser if logged in
const token = cookies.get("TOKEN");
if (token) {
setAuth(true);
};
return auth ? <Component /> : <Navigate to="/" />
}
You need yo put setAuth in a useEffect
export default function ProtectedRoutes({component: Component, ...rest}) {
const [auth, setAuth] = useState(false);
React.useEffect(()=>{
const token = cookies.get("TOKEN");
if (token) {
setAuth(true);
}
},[auth]);
return auth ? <Component /> : <Navigate to="/" />
}
In ProtectedRoutes component, you're setting a state (setAuth in this case) directly inside the component, this is what happens when you do that:
React re-renders a component every time a state change is detected thus when you wrote
export default function ProtectedRoutes({component: Component, ...rest}) {
...
if (token) {
setAuth(true);
};
...
}
you're running setAuth(sets a new state true) every time the component renders(re-renders) and in turn, re-rendering the component every time because of the state change which is why have an infinite loop there.
, this works like this:
It runs everytime a dependency changes(passed as an array), and when you pass an empty array it runs just twice - when the component mounts(renders the first time) and when it unmounts.
What you need to do is to pass an empty array as a dependency as below
import { useEffect } from 'react'
export default function ProtectedRoutes({component: Component, ...rest}) {
...
useEffect(() => {
if (token) {
setAuth(true);
};
}, [])
...
}
this setAuth just once when the component mounts

What is the best way to make private route in reactjs

I made a private route in my app basically all the route in my app is private. So to make Private route I added a state isAuthenticated which is false by default but when user login in it becomes true. So on this isAuthenticated state I implemented a condition in private route component. But the problem with this is when user is logged in and refresh the page. I get redirect to / home page. I don't want that.
I am use token authentication here.
Here is my PrivateRoute.js
import React from "react";
import { connect } from "react-redux";
import { Route, Redirect } from "react-router-dom";
const PrivateRoute = ({ isAuthenticated, component: Component, ...rest }) => (
<Route
{...rest}
render={(props) =>
isAuthenticated ? <Component {...props} /> : <Redirect to="/" />
}
/>
);
function mapStateToProps(state) {
return {
isAuthenticated: state.user.isAuthenticated,
};
}
export default connect(mapStateToProps)(PrivateRoute);
If all your routes when authenticated are private you can also just skip the autentication by route and use following
import PrivateApp from './PrivateApp'
import PublicApp from './PublicApp'
function App() {
// set isAuthenticated dynamically
const isAuthenticated = true
return isAuthenticated ? <PrivateApp /> : <PublicApp />
}
That way you do not need to think for each route if it is authenticated or not and can just define it once and depending on that it will render your private or public app. Within those apps you can then use the router normally and do not need to think who can access which route.
If you validate the token against a server (which you should) then it's an asynchronous operation and isAuthenticated will be falsy initially, causing the redirect on refresh.
You one way around this is an isAuthenticating state, e.g.
import React from "react";
import { connect } from "react-redux";
import { Route, Redirect } from "react-router-dom";
const PrivateRoute = ({ isAuthenticated, isAuthenticating, component: Component, ...rest }) => (
<Route
{...rest}
render={(props) => {
if( isAuthenticating ){ return <Spinner /> }
return isAuthenticated ? <Component {...props} /> : <Redirect to="/" />
}
}
/>
);
function mapStateToProps(state) {
return {
isAuthenticated: state.user.isAuthenticated,
isAuthenticating: state.authentication.inProgress
};
}
export default connect(mapStateToProps)(PrivateRoute);
isAuthenticating should be true by default then set to false when the server response is received and the user.isAuthenticated state is known.

Converting stateless React component having arguments to stateful

Inside my React JS project, I am working on the PrivateRoutes.
I have gone through this example of private routing and authenticating using react-router-dom.
https://reacttraining.com/react-router/web/example/auth-workflow
According to this documentation, they have created a PrivateRoute as a stateless component.
But my requirement is to convert it to stateful React component as I want to connect my PrivateRoute component to redux store.
Here is my code.
stateless component
import React from 'react';
import {Route, Redirect} from 'react-router-dom';
import {auth} from './Authentication';
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
auth.isAuthenticated ? (
<Component {...props} />
) : (
<Component {...props} action="login"/>
)
}
/>
);
export default PrivateRoute;
I converted this component to stateful React component like this.
stateful React component
import React from 'react';
import {Route, Redirect} from 'react-router-dom';
import {auth} from './Authentication';
import {connect} from 'react-redux';
class PrivateRoute extends React.Component {
render({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={props =>
this.props.customer.isAuthenticated ? (
<Component {...props} />
) : (
<Component {...props} action="login"/>
)
}
/>
);
}
}
export default connect(state => state)(PrivateRoute);
Here, I am reading the data from redux store to check whether the user is authenticated or not.
But the way I am converting the stateless component to stateful isn't correct.
Am I passing the arguments render({ component: Component, ...rest }) correctly?
Will connecting the PrivateRoute with redux store create any problem with props as state=>state will map state to props as well as ...rest will have props object?
Not sure what is happening inside the code.
Update
AppRouter.js
import React from 'react';
import {BrowserRouter, Route, Switch} from 'react-router-dom';
import {TransitionGroup, CSSTransition} from 'react-transition-group';
import PrivateRoute from './PrivateRoute';
import HomePage from './../components/HomePage';
import AboutUs from './../components/AboutUs';
import ContactUs from './../components/ContactUs';
import PageNotFound from './../components/PageNotFound';
import RestaurantList from '../components/RestaurantList';
import RestaurantMenu from '../components/RestaurantMenu';
import UserDetails from '../components/UserDetails';
import OrderConfirmation from '../components/OrderConfirmation';
import CustomerAccount from '../components/CustomerAccount';
import Logout from '../components/sections/Logout';
export default () => {
return (
<BrowserRouter>
<Route render={({location}) => (
<TransitionGroup>
<CSSTransition key={location.key} timeout={300} classNames="fade">
<Switch location={location}>
<Route path="/" component={HomePage} exact={true}/>
<Route path="/about" component={AboutUs} />
<Route path="/contact" component={ContactUs} />
<Route path="/restaurants" component={RestaurantList} />
<Route path="/select-menu" component={RestaurantMenu} />
<PrivateRoute path="/user-details" component={UserDetails} />
<PrivateRoute path="/order-confirmation" component={OrderConfirmation} />
<PrivateRoute path="/my-account" component={CustomerAccount} />
<PrivateRoute path="/logout" component={Logout} />
<Route component={PageNotFound} />
</Switch>
</CSSTransition>
</TransitionGroup>
)} />
</BrowserRouter>
);
}
In general, converting a stateless functional component (SFC) to a Component is done like this:
Create the class shell for it.
Copy the SFC's body to the render method. If the SFC was an arrow function, add a return as necessary to render.
Change any references to props in the render method to this.props (or just add const { props } = this; at the top). SFCs receive their props in their arguments, but a component receives them as arguments to its constructor; the default constructor will save them as this.props.
In your case, it's using destructuring on its arguments, so you could do the same with this.props on the right-hand side of the destructuring:
const { component: Component, ...rest } = this.props;
That's it. In your code, you've added parameters to the render function, but it doesn't get called with any arguments, and you've only changed props to this.props a bit haphazardly (including changing auth.isAuthenticated to this.props.customer.isAuthenticated for some reason).
So applying 1-3 above:
// #1 - the shell
class PrivateRoute extends React.Component {
// #2 - `render`, with the body of the SFC inside
render() {
// #3 - destructure `this.props`
const { component: Component, ...rest } = this.props;
// #2 (part 2) - add `return`
return <Route
{...rest}
render={props =>
auth.isAuthenticated ? (
<Component {...props} />
) : (
<Component {...props} action="login"/>
)
}
/>;
}
}
Your stateful component should be:
class PrivateRoute extends React.Component {
render() {
const { component: Component, ...rest } = this.props;
return (
<Route
{...rest}
render={props =>
this.props.customer.isAuthenticated ? (
<Component {...props} />
) : (
<Component {...props} action="login"/>
)
}
/>
);
}
}
Please see that there is some issue in render parameter of Route. Here you have props as function param but still using this.props.customer, don't know the use case hence please fix it as per your application.
Apart from it Component and all the other data is already there in props of the component. It won't be available in parameter of render method in component. Same destructuring as available in stateless component can be written in render method as shown in code above.
Will connecting the PrivateRoute with redux store create any problem with props?
Yes, it would. The way you have connected to the store will make store data available in props of component but external props passed to component will not be available.
For that you have to handle it in mapStateToProps function:
const mapStateToProps = (state, ownProps) => ({
...state,
...ownProps
});
Here mapStateToProps has second parameter which has the external own props passed to component. So you have to return it as well to make it available in component props.
Now connect would be like:
export default connect(mapStateToProps)(PrivateRoute);
I was having two queries.
1) How to convert to Stateful Functional Component?
2) After connecting to the redux store will the props create a problem?
My first query was solved by the answer provided by T.J.Crowder.
For a second query, I tried connecting the redux store to the PrivateRoute and I did get the data I was looking for.
Here is the code which worked for me.
import React from 'react';
import {Route, Redirect} from 'react-router-dom';
import {connect} from 'react-redux';
class PrivateRoute extends React.Component {
render() {
const { component: Component, ...rest } = this.props;
const {customer} = this.props;
return <Route
{...rest}
render={props =>
customer.isAuthenticated ? (
<Component {...props} />
) : (
<Component {...props} action="login"/>
)
}
/>;
}
}
export default connect(state => state)(PrivateRoute);
Using this code I got the data that is coming from the routes, as well as the redux state inside the props.
This is getting data coming from the routes
const { component: Component, ...rest } = this.props;
This is the data coming from the redux store.
const {customer} = this.props;
#T.J.Crowder has already written how to convert stateless component to stateful component in those 3 steps. so i will just write about connecting component to redux store like you did.
I think connected components should always define mapStateToProps and explicitly declare which data they depend on from the state.
because the connected component rerenders if the connected property changes. so it would be a bad idea to connect the whole application state to a component. as it would mean that wheneever anything changes in application state rerender all connected components.
better we define explicitly like the following that we depend on a property called data (or anything you have) from the state. so in this case this component will only rerender if state.data changes it wont rerender if state.xyz changes.
and this way you can take state.data and name it as you wish so it would not conflict with any existing props of the component.
const mapStateToProps = (state, ownProps) => ({
data: state.data
});
export default connect(mapStateToProps)(PrivateRoute);

Why React-router v4 <Link/> does not work (Changes url but not rendering content)?

I have server side React/Redux/Express app.
React-router v4 provides solution for a server app with Switch and I need to use something to change location from my NavBar component
App
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { Switch, Route, Redirect, Link} from 'react-router-dom'
import FirstPage from './FirstPage'
import Test from './Test'
import LoginPage from './login/LoginPage'
import NoMatch from '../components/NoMatch'
import NavBar from '../components/NavBar'
import * as loginActions from '../actions/login'
import 'bootstrap/dist/css/bootstrap.css';
class App extends Component {
render(){
return (
<div>
<NavBar/>
<h1>EffortTracker v3.0.1</h1>
<Switch >
<Route exact path="/" render={loginRedirect(<FirstPage/>)}/>
<Route path="/first" render={loginRedirect(<FirstPage/>)}/>
<Route path="/second" render={loginRedirect(<Test/>)}/> />
<Route path="/login" render={()=><LoginPage {...this.props.login}/>} />
<Route component={NoMatch}/>
</Switch>
<Link to={'/first'}>First</Link>
</div>
)
}
}
const loginRedirect=(component) => {
if(!isLoggedIn()) return ()=> <Redirect to='login'/>
return ()=> component
}
const isLoggedIn= ()=>{
let token = localStorage.getItem('token')
if (token !== null)return false
else return true
}
const mapStateToProps = state => ({
login: state.login,
error: state.error,
isLoading: state.isLoading,
})
const mapDispatchToProps = dispatch => ({
loginActions: bindActionCreators(loginActions, dispatch)
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(App)
Need to change from
NavBar
import React from 'react'
import { Link, NavLink } from 'react-router-dom'
import classnames from 'classnames'
const NavBar =()=> {
return (
<nav className={classnames("navbar", "navbar-inverse bg-inverse")}>
<form className="form-inline">
<Link to={'/'}>
<button className={classnames("btn", "btn-sm", "align-middle", "btn-outline-secondary")}
type="button">
Smaller button
</button>
</Link>
<NavLink to='/login'>
Login
</NavLink>
</form>
</nav>
)
}
export default NavBar
If I navigate it manually from browser url its work just fine but if I click a Link or NavLink url is updated but not the App Switch. Also I have an issue when loginRedirect to /login it does not appear and need to refresh page (possible that this two is related ).
How to fix this?
I think the problem here is with redux .. because it blocks rerendering the components as long as the props didn't change,
This is because connect() implements shouldComponentUpdate by default, assuming that your component will produce the same results given the same props and state.
The best solution to this is to make sure that your components are pure and pass any external state to them via props. This will ensure that your views do not re-render unless they actually need to re-render and will greatly speed up your application.
If that’s not practical for whatever reason (for example, if you’re using a library that depends heavily on React context), you may pass the pure: false option to connect():
function mapStateToProps(state) {
return { todos: state.todos }
}
export default connect(mapStateToProps, null, null, {
pure: false
})(TodoApp)
here are links for more explanation:
react-redux troubleshooting section
react-router DOCS
If using Redux, the redux connect HOC overrides the shouldComponentUpdate lifecycle method on your component and checks for props and state change this can confuse the React Router. Something like a user clicking a link will not necessarily change the state or props as is, leading to not re-rendering the components in the routeing context.
The documentation for react router states a solution for this problem:
Wrap the component with the withRouter HOC
import { Route, Switch, withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
const Main = (props) => (
<main>
<Switch>
<Route exact path='/' component={SAMPLE_HOME}/>
<Route path='/dashboard' component={SAMPLE_DASHBOARD}/>
</Switch>
</main>
)
export default withRouter(connect()(Main))
Also, as an enclosing route component will pass props with a location property, you can pass that into the child component and that should achieve the desired behaviour.
https://reacttraining.com/react-router/web/guides/dealing-with-update-blocking

Programmatically Navigate using react-router

I am developing an application in which I check if the user is not loggedIn. I have to display the login form, else dispatch an action that would change the route and load other component. Here is my code:
render() {
if (isLoggedIn) {
// dispatch an action to change the route
}
// return login component
<Login />
}
How can I achieve this as I cannot change states inside the render function.
Considering you are using react-router v4
Use your component with withRouter and use history.push from props to change the route. You need to make use of withRouter only when your component is not receiving the Router props, this may happen in cases when your component is a nested child of a component rendered by the Router and you haven't passed the Router props to it or when the component is not linked to the Router at all and is rendered as a separate component from the Routes.
import {withRouter} from 'react-router';
class App extends React.Component {
...
componenDidMount() {
// get isLoggedIn from localStorage or API call
if (isLoggedIn) {
// dispatch an action to change the route
this.props.history.push('/home');
}
}
render() {
// return login component
return <Login />
}
}
export default withRouter(App);
Important Note
If you are using withRouter to prevent updates from being blocked by
shouldComponentUpdate, it is important that withRouter wraps the
component that implements shouldComponentUpdate. For example, when
using Redux:
// This gets around shouldComponentUpdate
withRouter(connect(...)(MyComponent))
// This does not
connect(...)(withRouter(MyComponent))
or you could use Redirect
import {withRouter} from 'react-router';
class App extends React.Component {
...
render() {
if(isLoggedIn) {
return <Redirect to="/home"/>
}
// return login component
return <Login />
}
}
With react-router v2 or react-router v3, you can make use of context to dynamically change the route like
class App extends React.Component {
...
render() {
if (isLoggedIn) {
// dispatch an action to change the route
this.context.router.push('/home');
}
// return login component
return <Login />
}
}
App.contextTypes = {
router: React.PropTypes.object.isRequired
}
export default App;
or use
import { browserHistory } from 'react-router';
browserHistory.push('/some/path');
In react-router version 4:
import React from 'react'
import { BrowserRouter as Router, Route, Redirect} from 'react-router-dom'
const Example = () => (
if (isLoggedIn) {
<OtherComponent />
} else {
<Router>
<Redirect push to="/login" />
<Route path="/login" component={Login}/>
</Router>
}
)
const Login = () => (
<h1>Form Components</h1>
...
)
export default Example;
Another alternative is to handle this using Thunk-style asynchronous actions (which are safe/allowed to have side-effects).
If you use Thunk, you can inject the same history object into both your <Router> component and Thunk actions using thunk.withExtraArgument, like this:
import React from 'react'
import { BrowserRouter as Router, Route, Redirect} from 'react-router-dom'
import { createBrowserHistory } from "history"
import { applyMiddleware, createStore } from "redux"
import thunk from "redux-thunk"
const history = createBrowserHistory()
const middlewares = applyMiddleware(thunk.withExtraArgument({history}))
const store = createStore(appReducer, middlewares)
render(
<Provider store={store}
<Router history={history}>
<Route path="*" component={CatchAll} />
</Router
</Provider>,
appDiv)
Then in your action-creators, you will have a history instance that is safe to use with ReactRouter, so you can just trigger a regular Redux event if you're not logged in:
// meanwhile... in action-creators.js
export const notLoggedIn = () => {
return (dispatch, getState, {history}) => {
history.push(`/login`)
}
}
Another advantage of this is that the url is easier to handle, now, so we can put redirect info on the query string, etc.
You can try still doing this check in your Render methods, but if it causes problems, you might consider doing it in componentDidMount, or elsewhere in the lifecycle (although also I understand the desire to stick with Stateless Functional Compeonents!)
You can still use Redux and mapDispatchToProps to inject the action creator into your comptonent, so your component is still only loosely connected to Redux.
This is my handle loggedIn. react-router v4
PrivateRoute is allow enter path if user is loggedIn and save the token to localStorge
function PrivateRoute({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={props => (localStorage.token) ? <Component {...props} /> : (
<Redirect
to={{
pathname: '/signin',
state: { from: props.location },
}}
/>
)
}
/>
);
}
Define all paths in your app in here
export default (
<main>
<Switch>
<Route exact path="/signin" component={SignIn} />
<Route exact path="/signup" component={SignUp} />
<PrivateRoute path="/" component={Home} />
</Switch>
</main>
);
Those who are facing issues in implementing this on react-router v4. Here is a working solution for navigating through the react app programmatically.
history.js
import createHistory from 'history/createBrowserHistory'
export default createHistory()
App.js OR Route.jsx. Pass history as a prop to your Router.
import { Router, Route } from 'react-router-dom'
import history from './history'
...
<Router history={history}>
<Route path="/test" component={Test}/>
</Router>
You can use push() to navigate.
import history from './history'
...
render() {
if (isLoggedIn) {
history.push('/test') // this should change the url and re-render Test component
}
// return login component
<Login />
}
All thanks to this comment: https://github.com/ReactTraining/react-router/issues/3498#issuecomment-301057248
render(){
return (
<div>
{ this.props.redirect ? <Redirect to="/" /> :'' }
<div>
add here component codes
</div>
</div>
);
}
I would suggest you to use connected-react-router https://github.com/supasate/connected-react-router
which helps to perform navigation even from reducers/actions if you want.
it is well documented and easy to configure
I was able to use history within stateless functional component, using withRouter following way (needed to ignore typescript warning):
import { withRouter } from 'react-router-dom';
...
type Props = { myProp: boolean };
// #ts-ignore
export const MyComponent: FC<Props> = withRouter(({ myProp, history }) => {
...
})
import { useNavigate } from "react-router-dom"; //with v6
export default function Component() {
const navigate = useNavigate();
navigate.push('/path');
}
I had this issue and just solved it with the new useNavigate hook in version 6 of react-router-dom

Categories

Resources