I'm relatively new to React and Redux and learning them through my personal project.
The issue here is that isAuthed
cannot use the updated Redux state after rest.dispatch(actions.isValidUser(json))
is executed. As far as I know, the Redux state is updated by the action. (But I don't see connect()
is called after the update...I don't know if this is associated with this problem.)
Also I tried using Redux-thunk in my action file to fetch data from an API endpoint and using useEffect(), but it didn't solve the issue. Could you please help me out?
Thank you in advance.
**ProtedtedRoute.jsx**
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import * as actions from '../actions/actions';
function ProtectedRoute({ component: Component, isAuthed, ...rest }) {
async function verifyUser() {
// checking if a user is valid by checking JWT
const res = await fetch(ENDPOINT, reqOptions);
if (res.status === 200) {
const json = await res.json();
rest.dispatch(actions.isValidUser(json));
} else {
// failure handling
};
};
verifyUser();
return (
<Route
{...rest}
render={(props) => isAuthed == true ? <Component {...props} /> : <Redirect to={{ pathname: '/login', state: { from: props.location } }} />}
/>
);
};
export default connect(state => {
return {
isAuthed: state.isAuthenticated
}
})(ProtectedRoute);
**reducer.js**
const initState = {
data: {},
// when a user is valid, it will be ```true```
isAuthenticated: false
}
**App.js**
function App() {
return (
<Provider store={store}>
<BrowserRouter>
<div>
<div className="content">
<Switch>
<Route exact path="/" component={Home} />
<PublicRoute path="/login" component={LogIn} />
<PublicRoute path="/signup" component={SignUp} />
<ProtectedRoute path="/dashboard" component={Dashboard} />
</Switch>
...
**Login.jsx**
const res = await fetch(ENDPOINT, { reqOptions});
if (res.status === 200) {
props.history.push('/dashboard');
else{
// error handling
}
You don't want a function call like verifyUser();
just floating in the component. It needs to be inside a useEffect
hook.
Your Login
component fetches the endpoint before you redirect to Dashboard
, so you should not need to fetch the endpoint again in order to access the Dashboard
through PrivateRoute
.
You can change your initialState
to include isAuthenticated: undefined
as in "we don't know if they are authenticate or not because we haven't checked yet."
Then in PrivateRoute
, we only need to call verifyUser
if the value of isAuthed
is undefined
meaning that we haven't checked yet. If it's true
or false
we just use that existing value.
We still have a bit of a problem with the aysnc flow because we don't want to to Redirect
off of the PrivateRoute
before verifyUser
has finished. For that, we can conditionally render a loading state that shows while awaiting credentials.
I don't know that this is the most elegant solution but it should work
function ProtectedRoute({ component: Component, isAuthed, ...rest }) {
async function verifyUser() {
// checking if a user is valid by checking JWT
const res = await fetch(ENDPOINT, reqOptions);
if (res.status === 200) {
const json = await res.json();
rest.dispatch(actions.isValidUser(json));
} else {
// failure handling
}
}
useEffect(() => {
if (isAuthed === undefined) {
verifyUser();
}
}, [isAuthed]); //re-run when isAuthed changes
return (
<Route
{...rest}
render={(props) =>
isAuthed === undefined ? (
<Loading />
) : isAuthed === true ? (
<Component {...props} />
) : (
<Redirect
to={{ pathname: "/login", state: { from: props.location } }}
/>
)
}
/>
);
}