Can we redirect to in reduxSaga - reactjs

I've a login page with HOC I pass component which must render be after successful login.
Here, if I check for isLoggedIn and redirect in render of sigin-in for then I get error
err: Invariant Violation: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops
saga.js
try{//call api
//put login success
<Redirect to="/app"/>
}
index.js
const AuthProfile=requireAuth(App);
In reactdom
<Route render={(props)=><AuthProfile {...props}/>} path="/app"/>
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import { bindActionCreators } from 'redux';
export default function (ComposedComponent) {
class Authenticate extends React.Component {
componentDidMount() {
console.log("12ra")
this._checkAndRedirect();
}
componentDidUpdate() {
this._checkAndRedirect();
}
_checkAndRedirect() {
const { isLoggedIn, redirect } = this.props;
if (!isLoggedIn) {
redirect();
}
}
render() {
console.log("28ra")
return (
<div>
{ this.props.isLoggedIn ? <ComposedComponent {...this.props} /> : null }
</div>
);
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn:state.signInReducer.isLoggedIn
};
};
const mapDispatchToProps = dispatch => bindActionCreators({
redirect: () => push('/')
}, dispatch)
//Authenticate.propTypes = propTypes
return connect(
mapStateToProps,
mapDispatchToProps
)(Authenticate);
}
Is HOC component correct or I'm missing on something?
Is it good practise to redirect in saga?
Can anyone please lemme know how do I get to app component after success i'm stuck there please help thanks
UPDATE
saga.js
yield put({type:LOGIN_SUCCESS,payload:data})
yield put(push('/app'))
index.js
Ex for Page1,Page2 I need
<AuthRoute
exact
path="/"
component={Page1}
redirectTo="/login"
authenticated={this.props.isLoggegIn}
/>
<AuthRoute
exact
path="/"
component={Page2}
redirectTo="/login"
authenticated={this.props.isLoggegIn}
/>

Following is the way where you can navigate from saga:
saga.js
import { push } from 'react-router-redux';
...
// after API call & validate user
yield put(push('/app'));
index.js
<Route strict exact path='/app' component={App}>
<AuthRoute
exact
path="/"
component={Yourcomponent}
redirectTo="/login"
authenticated={hasAccessToken()}
/>

I am going to tell you the best and the simplest way from which you can redirect in any part of the application(i.e. inside redux and outside as well).
Step: 1
Create history.js file.
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
Step : 2 Import it inside App.js file (or, where you have made routers of your application).
import React from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import * as ROUTES from '../../constants/Routes';
import history from './history';
const App = () => {
return (<Router history={history}>
<div className='App'>
<Switch>
<Route exact path={ROUTES.NOTIFICATIONS} component={Notification} />
<Route exacr path={ROUTES.USER_PROFILE} component={UserProfile} />
<Route exact path={ROUTES.SIGN_IN} component={SignInPage} />
<Route exact path={ROUTES.SIGN_UP} component={SignUpPage} />
<Route path='*' component={SignInPage} />
</Switch>
</div>
</Router>
);
};
export default App;
Step: 3 Inside Saga file just import the history.
import { push } from 'react-router-redux';
import { Auth } from "../service/api/Auth";
import history from '../containers/App/history';
import * as ACTION from '../constants/ActionType';
import { put, takeLatest, call } from "redux-saga/effects";
import { userLoginSuccess, userLoginFailed } from "../actions/auth";
export function* userLogin(loginData) {
const payLoad = loginData.payload;
try {
const loginUserData = yield call(Auth.userLoginApiCall, payLoad);
yield put(userLoginSuccess(TOKEN));
history.push('/posts'); // Redirect to Post Page
} catch (err) {
yield put(push(userLoginFailed()))
}
}
export default function* userLoginRequest() {
yield takeLatest(ACTION.USER_LOGIN_REQUEST, userLogin);
}

Related

Why is withRouter used with connect () breaking my react app?

Good evening everyone,
I have been trying to add withRouter to my react app so it does not break because of the connect function (see code below).
My code is working, but when i add withRouter to the line below, it breaks my app with the following message :
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
Error: Invariant failed: You should not use <withRouter(Connect(App)) /> outside a Router>
i found this topic : Invariant failed: You should not use <Route> outside a <Router> but it's not helping me with me issue when i try to replace with a single Router
Please find below the whole code used :
App.js
import React, {useEffect}from 'react';
import { BrowserRouter } from 'react-router-dom'
import { Route, Redirect} from 'react-router-dom'
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
import * as actions from './store/actions/index'
// Composants
import Layout from './components/hoc/Layout/Layout'
import BudgetHandler from './components/BudgetHandler/BudgetHandler'
import DashBoard from './components/DashBoard/DashBoard'
import Movements from './components/Movements/Movements'
import Home from './components/Home/Home'
import Logout from './components/Logout/Logout'
import classes from './App.module.css'
const App = (props) => {
useEffect(() => {
props.onTryAutoSignup()
},[])
let routes = <React.Fragment>
<Route path="/" exact component={Home} />
<Redirect to="/" />
</React.Fragment>
if(props.isAuthentificated) {
routes = <React.Fragment>
<Route path="/movements" component={Movements} />
<Route path="/dashboard" component={DashBoard} />
<Route path="/logout" component={Logout} />
<Route path="/handler" component={BudgetHandler} />
<Redirect to="/dashboard" />
</React.Fragment>
}
return (
<div className={classes.App}>
<BrowserRouter>
<Layout>
{routes}
</Layout>
</BrowserRouter>
</div>
);
}
const mapStateToProps = state => {
return {
isAuthentificated: state.auth.token !== null
}
}
const mapDispatchToProps = dispatch => {
return {
onTryAutoSignup: () => dispatch(actions.authCheckState())
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
And this is happening because i am trying to add this function to the useEffect hook to check permanently if the user is auth or not :
in actions/auth.js
export const authCheckState = () => {
return dispatch => {
const token = localStorage.getItem('token')
if(!token) {
dispatch(logout())
} else {
const expirationTime = new Date(localStorage.getItem('expirationDate'))
const userId = localStorage.getItem('userId')
if(expirationTime > new Date()){
dispatch(logout())
} else {
dispatch(finalSignIn(token, userId))
dispatch(checkAuthTimeout(expirationTime.getSeconds() - new Date().getSeconds()))
}
}
}
}
Thank you for your help
Have a good evening
withRouter can only be used in children components of element. In your case can be used with Movements, DashBoard and other childrens. use it while exporting Movements like
export default withRouter(Movements)
on Movements page.

history.push() is updating urls but not re-rendering components

I'm facing a problem with react-router-dom. I'm trying to use history.push for navigating after an action conducted. but the problem is createBrowserHistory from history is updating the urls but components are not re-rendering. I've used every solution from https://stackoverflow.com/. But it's still not working as expected.
However I found a reason behind it. As my components are wrapped with connect function connect is preventing the re-render. And there was a solution too, wrap the connect function with withRouter. I tried it too. But it's not working.
Here is My App.js
import React, { Component } from "react";
import { Router, Route } from "react-router-dom";
import history from "../history"
import Navbar from "./Navbar";
import LogIn from "./LogIn";
import StreamCreate from "./streams/StreamCreate";
import StreamDelete from "./streams/StreamDelete";
import StreamEdit from "./streams/StreamEdit";
import StreamList from "./streams/StreamList";
import StreamShow from "./streams/StreamShow";
import Profile from "./streams/Profile";
class App extends Component {
render() {
return (
<div>
<Router history={history}>
<div>
<Navbar />
<Route path="/" exact component={StreamList} />
<Route path="/streams/new" exact component={StreamCreate} />
<Route path="/streams/delete" exact component={StreamDelete} />
<Route path="/streams/edit" exact component={StreamEdit} />
<Route path="/streams/show" exact component={StreamShow} />
<Route path="/login" exact component={LogIn} />
<Route path="/my-streams" exact component={Profile} />
</div>
</Router>
</div>
);
}
}
export default App;
Here is the history.js
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
Action Creator:
import Streams from "../API/Streams";
import history from "../history";
export const createStreams = (formData) => async (dispatch, getState) => {
const { userId } = getState().auth;
const response = await Streams.post("/streams", { ...formData, userId });
dispatch({ type: "CREATE_STREAM", payload: response.data });
history.push("/")
};

Send request to check role before render specific component

I have a project structure like that:
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import {store, persistor} from './helpers';
import { Main } from './main';
import { PersistGate } from 'redux-persist/integration/react';
import { InitIDB } from './helpers/InitIDB';
require('./bootstrap');
InitIDB();
render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Main />
</PersistGate>
</Provider>,
document.getElementById('root')
);
This is entry point, and then the Main component:
import React from 'react';
import { Route, Switch, Router } from 'react-router-dom';
import { connect } from 'react-redux';
import { history } from '../helpers';
import {PrivateRoute} from "../components";
import { ProjectPage } from '../forms/project';
import { ProfilePage } from '../forms/profile';
import { Login } from '../forms/login';
class Main extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<React.Fragment>
<Router history={history}>
<Switch>
<Route exact path="/login" component={Login} />
<PrivateRoute exact path="/com/projects" component={ProjectPage} />
<PrivateRoute exact path="/com/profiles" component={ProfilePage} />
</Switch>
</Router>
</React.Fragment>
);
}
}
function mapState(state) {
const { alert } = state;
return { alert };
}
const actionCreators = {
clearAlerts: function() {}
}
const connectedApp = connect(mapState, actionCreators)(Main);
export { connectedApp as Main };
//PrivateRoute
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import {LayoutX} from '../forms/layout';
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
sessionStorage.getItem('user')
? <LayoutX><Component {...props} /></LayoutX>
: <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)} />
)
I want when user access private route, before that the program will send a request to server and it will return a response decide user can be access this page or not. I try to send a request with axios in PrivateRoute but it is async request and I can not get response before render. What should I do?
Your PrivateRoute should block render when waiting for the authentication status from API is returned.
You can take a look at a simple implementation of PrivateRoute I wrote in this codesandbox.

React Private Route higher order component?

I am trying to make a PrivateRoute component for react. Here is my higher order component. Can you tell me what is the problem with this.
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
export default ({ component: Component, ...rest }) => {
class PrivateRoute extends React.Component {
render() {
console.log("This is private route called");
if (this.props.profile) {
return (
<Route
{...rest}
render={props =>
this.props.profile.loggedIn === true ? (
<Component {...props} />
) : (
<Redirect to="/login" />
)
}
/>
);
}
}
}
const mapStateToProps = state => ({
profile: state.profile
});
return connect(mapStateToProps)(PrivateRoute);
};
Here's how you can accomplish a protected route via a protected route component.
Working example: https://codesandbox.io/s/yqo75n896x
containers/RequireAuth.js
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import ShowPlayerRoster from "../components/ShowPlayerRoster";
import ShowPlayerStats from "../components/ShowPlayerStats";
import Schedule from "../components/Schedule";
const RequireAuth = ({ match: { path }, isAuthenticated }) =>
!isAuthenticated ? (
<Redirect to="/signin" />
) : (
<div>
<Route exact path={`${path}/roster`} component={ShowPlayerRoster} />
<Route path={`${path}/roster/:id`} component={ShowPlayerStats} />
<Route path={`${path}/schedule`} component={Schedule} />
</div>
);
export default connect(state => ({
isAuthenticated: state.auth.isAuthenticated
}))(RequireAuth);
routes/index.js
import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";
import Home from "../components/Home";
import Header from "../containers/Header";
import Info from "../components/Info";
import Sponsors from "../components/Sponsors";
import Signin from "../containers/Signin";
import RequireAuth from "../containers/RequireAuth";
import rootReducer from "../reducers";
const store = createStore(rootReducer);
export default () => (
<Provider store={store}>
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/info" component={Info} />
<Route path="/sponsors" component={Sponsors} />
<Route path="/protected" component={RequireAuth} />
<Route path="/signin" component={Signin} />
</Switch>
</div>
</BrowserRouter>
</Provider>
);
Or, if you want something that wraps all routes (instead of having to specify a protected route component). Then you can do something like the below.
Working example: https://codesandbox.io/s/5m2690nn6n
components/RequireAuth.js
import React, { Component, Fragment } from "react";
import { withRouter } from "react-router-dom";
import Login from "./Login";
import Header from "./Header";
class RequireAuth extends Component {
state = { isAuthenticated: false };
componentDidMount = () => {
if (!this.state.isAuthenticated) {
this.props.history.push("/");
}
};
componentDidUpdate = (prevProps, prevState) => {
if (
this.props.location.pathname !== prevProps.location.pathname &&
!this.state.isAuthenticated
) {
this.props.history.push("/");
}
};
isAuthed = () => this.setState({ isAuthenticated: true });
unAuth = () => this.setState({ isAuthenticated: false });
render = () =>
!this.state.isAuthenticated ? (
<Login isAuthed={this.isAuthed} />
) : (
<Fragment>
<Header unAuth={this.unAuth} />
{this.props.children}
</Fragment>
);
}
export default withRouter(RequireAuth);
routes/index.js
import React from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import Home from "../components/Home";
import Players from "../components/Players";
import Schedule from "../components/Schedule";
import RequireAuth from "../components/RequireAuth";
export default () => (
<BrowserRouter>
<RequireAuth>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/players" component={Players} />
<Route path="/schedule" component={Schedule} />
</Switch>
</RequireAuth>
</BrowserRouter>
);
Or, if you want something a bit more modular, where you can pick and choose any route, then you can create a wrapper HOC. See this example (while it's written for v3 and not for authentication, it's still the same concept).
It looks like your render function's only return is inside of an if block, so its returning null. You need to fix the logic to just return the Route and make profile a required prop in your proptypes check instead of using an if block.
PrivateRoute.propTypes = {
profile: PropTypes.object.isRequired
};

Infinite Loop With Higher Order React Redux Component

I am having some trouble with an infinite loop when using a Higher Order Component to handle authenticated routes. The authentication is very basic, and is just a boolean of true or false in the redux state.
When the app initially starts, it checks to see if there is a user stored in localStorage. If it does not return null, we immediately dispatch a SIGN_IN action.
import React from 'react'
import ReactDOM from 'react-dom'
import { HashRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import reducers from './reducers'
import reduxThunk from 'redux-thunk'
import { SIGN_IN } from './actions/types'
import logger from 'redux-logger'
const createStoreWithMiddleware = applyMiddleware(reduxThunk, logger)(createStore)
const store = createStoreWithMiddleware(reducers)
import App from './components/App'
const user = localStorage.getItem('user')
if(user) {
store.dispatch({ type: SIGN_IN })
}
ReactDOM.render (
<Provider store={store}>
<HashRouter>
<App />
</HashRouter>
</Provider>,
document.querySelector('.main')
)
This is the higher order component.
import React, { Component } from 'react';
import { connect } from 'react-redux';
export default function(ComposedComponent) {
class RequireAuth extends Component {
componentWillMount = () => {
if(!this.props.auth)
this.props.history.push('/')
}
render() {
return <ComposedComponent {...this.props} />
}
}
function mapStateToProps(state) {
return { auth: state.auth };
}
return connect(mapStateToProps)(RequireAuth);
}
The logic inside of componentWillMount seems to be what is causing the infinite loop, but I really do not know why. It seems to prevent me from going to any other routes, but as you can see in the console output below, it blows up in extraordinary fashion in doing so.
If it helps, here is the component, where I am wrapping my components with the HOC.
export default class App extends Component {
constructor(props) {
super(props)
}
render() {
return (
<div className="app"
style={{
backgroundImage: 'url("./img/base-bg.jpg")',
backgroundSize: 'cover',
backgroundPosition: 'center center',
backgroundRepeat: 'no-repeat'
}}
>
<Switch>
<Route path="/" exact component={RequireAuth(Intro)} />
<Route path="/landing" exact component={RequireAuth(Intro)} />
<Route path="/journey" component={RequireAuth(Exhibit)} />
<Redirect to="/landing" />
</Switch>
<Footer />
</div>
)
}
}
I am using React Router V4 if that helps. And for clarification, contains a login form, and a landing page. If the user is not authenticated, intro shows a login form, and when authed, the landing page should be shown. Perhaps this is where I am messing things up, and shouldn't be nesting my components. I am unsure though.
This boiled down to the hackalicious nesting and how I was wrapping my components with RequireAuth. Instead of having Login and Landing inside of a component called Intro, I scrapped that and changed my route config to this:
<Switch>
<Route path="/" exact component={Login} />
<Route path="/landing" exact component={RequireAuth(Landing)} />
</Switch>
Here is my HOC now, pretty much the same.
export default function(ComposedComponent) {
class RequireAuth extends Component {
componentDidMount = () => {
if(!this.props.auth)
this.redirectToLogin()
}
componentWillUpdate = nextProps => {
if(!nextProps.auth)
this.redirectToLogin()
}
redirectToLogin = () => this.props.history.push('/')
render() {
return <ComposedComponent {...this.props} />
}
}
function mapStateToProps(state) {
return { auth: state.auth };
}
return connect(mapStateToProps)(RequireAuth);
}

Resources