Reactjs router onEnter hook authentication cause infinite loop - reactjs

I'm trying to redirect logged in user on dashboard page.
Here is my code:
function requireAuth(nextState, replace) {
if(isLoggedIn()) {
console.log("user is logged in");
replace('/dashboard');
} else {
replace('/');
}
}
const Root = () => {
return (
<div className="container">
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route onEnter={requireAuth}>
<Route path="dashboard" component={AllEvents}/>
</Route>
</Route>
</Router>
</div>
)
}
When the user is logged in, my application is running into a loop with a requireAuth method.
Here is the screenshot of the console.
I've already considered two similar questions on StackOverflow which are:
react Maximum call stack size exceeded
React-router, onEnter cause infinite loop with authentication
I've tried both of them, but unfortunately, those examples didn't help me. (I'm also a beginner in React)
Please, tell me what is wrong with my code?

You get an infinite loop because if the user is logged it always redirect him to /dashboard and repeat the redirecting process starting from / and again hitting requireAuth.
Try:
function onlyUnAuthenticated(nextState, replace, callback) {
if(isLoggedIn()) {
replace('/dashboard');
}
callback();
}
function onlyAuthenticated(nextState, replace, callback) {
if(!isLoggedIn()) {
replace('/');
}
callback();
}
const Root = () => {
return (
<div className="container">
<Router history={browserHistory}>
<Route path="/" component={App} onEnter={onlyUnAuthenticated}>
<Route path="dashboard" component={AllEvents} onEnter={onlyAuthenticated}/>
</Route>
</Router>
</div>
)
}
I think that you will have to use callback in the hook.

Related

Redirecting when user is authenticated

I am trying to build a role based access control React app.
My vision was that when the App mounts, it checks if user token exists. If it does, it runs checkAuthToken() and sets the state accordingly.
Where I am struggling is: redirection doesn't work as I expect it to.
Here is my code:
in App.js
function App() {
const { isAuthenticated, user } = useSelector(state => {
return state.userState;
});
const dispatch = useDispatch();
useEffect(() => {
checkAuthToken();
}, []);
return (
<Router>
<Switch>
<Route exact path='/'>
{!isAuthenticated ? (
<Redirect to='/login'/>
) : (
<Redirect to={`/${user.role}`} />
)}
</Route>
<Route
path="/login"
render={() => {
return <Login />;
}}
/>
<Route
path="/admin"
render={() => {
return <AdminDashboard user={user} />;
}}
/>
<Route
path="/staff"
render={() => {
return <OrderMenu user={user} />;
}}
/>
<Route component={ErrorPage} />
</Switch>
</Router>
);
}
export default App;
My understanding is React rerenders when state or props change. When I get the updated state from Redux, the app component should rerender and thus go through the isAuthenticated check and redirect accordingly, but i'm being proven wrong.
Currently, if there's no token, it redirects to Login route. After user logs in, token is set in localStorage. So to test it out, I close and open a new tab, try to go to path / expecting it to redirect me to /[admin|staff] route since checkAuthToken would successfully set the state but redirect doesn't work and just lands on /login. However, I could access /[admin|staff] if I type in manually.

Returning a Redirect within then of an async function

I developed a login service function with axios. In my login component I call the service as I click the login button.
All works fine with one exception: Page doesn't redirect to /home (route exists).
const loginRequest = async () => {
return await loginService(username, password).then(
(loginResponse) => {
console.log('loginResponse', loginResponse);
if(loginResponse.status == 'success') {
setError('')
return <Redirect to='/home' />
} else {
setError('authenticate_failed')
}
}
)
}
My routing is like this:
<Router>
<Switch>
<Route exact path="/home" component={Home} />
<Route exact path="/login" component={Login} />
</Switch>
</Router>
What am I missing?
When you return Redirect under loginRequest, it's just the return value of onClick function of login button.
Not the render function.
You should use Router to redirect to login page.
this.props.history.push('/home')

Can't get Params from id routes

I'm stuck with a problem in my project. I'm trying to show a component and work with the this.props.match.params, but no matter what I do I get undefined.
Routes:
const App = () => (
<BrowserRouter>
<Fragment>
<Header/>
<main>
<Switch>
<Route path="/show/:id" component={Show}/>
<Route path="/" component={Home}/>
</Switch>
</main>
</Fragment>
</BrowserRouter>
);
export default App;
then I have a handler on my home route:
async handleSubmit(searchId) {
const id = await DwellingService.findSiocId(searchId);
if (id) {
this.props.history.push(`/show/${id}`);
}
}
and finally on my show component
componentDidMount() {
console.log(this.props.match.params)
const {id} = this.props.match.params;
if (id) {
this.props.requestFindDwelling(id);
}
}
So I have been researching and I think is not a react router problem, first when I try to access the routes by typing them I was getting unexpected > on my bundle.js which was solved adding <base href="/" /> on the index.html.
Now my component is rendering ok by the console.log of the show component is giving me this:
isExact:false
params:{}
path:"/show"
url:"/show"
When I started the project to be able to use browserhistory and not getting error by refreshing the page I had to add this to my index file:
app.get('/*', function(req, res) {
res.sendFile(path.join(__dirname, './public/index.html'), function(err) {
if (err) {
res.status(500).send(err);
}
});
});
For the kind of error I get I'm supposing the route is not being found and is redirecting me to /show.
<Switch>
<Route path="/show/:id" component={Show}/>
<Route path="/" component={Home}/>
</Switch>
This will never render Home as Switch renders first thing that matches and / will match always the first route. Not sure if this will fix the problem but try and let me know:
<Switch>
<Route exact path="/" component={Home}/> // exact is important
<Route path="/show/:id" component={Show}/>
</Switch>

Can we include normal react component inside <Route />?

I want to do something like:
<Route>
<MyComponent someCondition={true/false}>
<Route1 />
....
</MyComponent>
</Route
To handle some conditional rendering. However, <MyComponent /> seems not mounted upon rendering.
My question is: can we include normal react component within <Route>? If not, is there a better way to handle conditional routing?
What exactly do you mean by conditional routing? Assuming you mean something like not letting a user hit a route if they aren't authenticated, you can use react-router's onEnter hooks . You can make a parent <Route> that doesn't have a component prop and just handles routing checks. I used some simple onEnter checks in this example.
// onEnter hooks for login and home page to redirect if necessary
const checkAuth = function (nextState, replace) {
const { user } = store.getState()
if (isEmpty(user)) {
replace('/')
}
}
const checkSkipAuth = function (nextState, replace) {
const { user } = store.getState()
if (!isEmpty(user)) {
replace('/home')
}
}
var Index = () => {
return (
<Provider store={store}>
<Router history={history}>
<Route path='/' component={Container}>
<IndexRoute component={Login} onEnter={checkSkipAuth} />
<Route path='home' component={Home} onEnter={checkAuth} />
<Route path='*' component={NoMatch} />
</Route>
</Router>
</Provider>
)
}

How to get react-router and react-redux to play nicely?

Preface I have no interest in react-router-redux I do not at all want location as part of my state tree.
Original set up was as follows:
let routes = (
<Router history={hashHistory}>
<Route path='/' component={App}>
<IndexRoute component={HomePage} onEnter={_ensureLoggedIn}/>
<Route path="login" component={ SessionForm }/>
</Route>
</Router>
)
let provider = (
<Provider store={store}>
{routes}
</Provider>
);
function _ensureLoggedIn(nextState, replace, callback) {
let hasBeenFetched = store.getState().currentUser.hasBeenFetched;
let loggedIn = !!store.getState().currentUser.id;
if (hasBeenFetched) {
_redirectIfNotLoggedIn();
} else {
store.dispatch(Actions.fetchCurrentUser());
}
function _redirectIfNotLoggedIn() {
if (!loggedIn) {
replace({}, "/login");
}
callback();
}
}
ReactDOM.render(provider, root);
with this attempt, no components have access to the store (the entire point of using provider..?)
Ok cool so some digging reveals that you routes have to be generated inside of provider or else it doesnt work.
I replace {routes} inside Provider with the actual list of routes.
Still doesn't work... the most important aspect here is disabling access to the majority of routes if the user is not logged in. Its very easy to ensure this when all the work is done in the index.js file, but it is going to get messy if I have to start passing the store around (the entire point of using provider........)

Resources