Reputation:
I have a PrivateRoute that verify if a token is valid and it's an async function. The problem is: my validation to render a view is not working, it's always rendering:
App.js
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: "/", state: { from: props.location } }} />
)
}
/>
);
const Routes = () => (
<BrowserRouter>
<Fragment>
<Switch>
<Route exact path="/" component={SignIn} />
<PrivateRoute path="/app" component={App} />
</Switch>
<ModalContainer />
</Fragment>
</BrowserRouter>
);
export default Routes;
auth.js
import axios from 'axios'
export const isAuthenticated = async () => {
const isValidRequest = false;
const resp = await axios.get('http://127.0.0.1:8080...')
data = resp.data;
console.log(data);
....// more code
return isValidRequest;
}
How can I ensure that PrivateRoute will wait for function isAuthenticated()
?
update 1:
const Routes = () => {
const [state, setState] = useState({isLoading: true, authenticated: false});
useEffect(() => {
async function checkAuth() {
const isAuth = await isAuthenticated();
setState({isLoading: false, authenticated: isAuth});
}
}, []);
if(state.isLoading) {
return <div>Loading....</div>
}
return (
<BrowserRouter>
<Fragment>
<Switch>
<Route exact path="/" component={SignIn} />
<PrivateRoute path="/app" isAuthenticated={state.authenticated} component={App} />
</Switch>
<ModalContainer />
</Fragment>
</BrowserRouter>
);
}
Upvotes: 2
Views: 857
Reputation: 282160
Instead of calling isAuthenticated
in all PrivateRoutes
, you call it once in your Routes component
so that the check is only performed once and then pass on the value as prop. Also note that while the data is being fetched maintain a loading state
Note, that your isAuthenticated
function is an async function so you have to wait till the promise is resolved. You can use async-await
or go by the traditional promise approach
const PrivateRoute = ({ component: Component, isAuthenticated, ...rest }) => (
<Route
{...rest}
render={props =>
isAuthenticated ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: "/", state: { from: props.location } }} />
)
}
/>
);
const Routes = () => {
const [state, setState] = useState({isLoading: true, authenticated: false});
useEffect(() => {
async function checkAuth() {
const isAuth = await isAuthenticated();
setState({isLoading: false, authenticated: isAuth});
}
checkAuth();
}, []);
if(state.isLoading) {
return <Loader/>
}
return (
<BrowserRouter>
<Fragment>
<Switch>
<Route exact path="/" component={SignIn} />
<PrivateRoute path="/app" isAuthenticated={state.authenticated} component={App} />
</Switch>
<ModalContainer />
</Fragment>
</BrowserRouter>
);
}
export default Routes;
Update: Since you do not use v16.8.0 or above of react, you can implement the above logic by using a class componnet
class Routes extends React.Component {
state = {isLoading: true, authenticated: false};
async componentDidMount() {
const isAuth = await isAuthenticated();
this.setState({isLoading: false, authenticated: isAuth});
}
render() {
if(state.isLoading) {
return <Loader/>
}
return (
<BrowserRouter>
<Fragment>
<Switch>
<Route exact path="/" component={SignIn} />
<PrivateRoute path="/app" isAuthenticated={state.authenticated} component={App} />
</Switch>
<ModalContainer />
</Fragment>
</BrowserRouter>
);
}
}
export default Routes;
Upvotes: 1
Reputation: 609
In order to wait for the promise to be resolved you can show a loading message when the result of isAuthenticated()
is undefined
:
const PrivateRoute = ({ component: Component, ...rest }) => {
const isAuthenticated = isAuthenticated();
if (isAuthenticated === undefined) {
return <p>
Loading ...
</p>
}
return isAuthenticated ?
<Route {...rest} render={props => <Component {...props}/>} /> :
<Redirect to={{ pathname: "/", state: { from: props.location } }} />
}
Upvotes: 0
Reputation: 577
I would suggest you store your progress and return depending on it:
const PrivateRoute = ({ component: Component, ...rest }) => (
const [finished, setFinished] = useState(false);
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
isAuthenticated().then(result => {
setFinished(true);
setIsAuthenticated(result);
});
}, []);
<Route
{...rest}
render={props =>
finished && isAuthenticated ? (
<Component {...props} />
) : finished && !isAuthenticated ? (
<Redirect to={{ pathname: "/", state: { from: props.location } }} />
) : null
}
/>
);
Upvotes: 0
Reputation: 2250
It could be because isAuthenticated()
returns a Promise which gets seen as a truthy value and therefore the ternary condition will render the component.
Give this a try instead:
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={async (props) => {
const authenticated = await isAuthenticated();
return authenticated ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: "/", state: { from: props.location } }} />
)
}
}
/>
);
I think this should work unless the render
prop for the Route
component doesn't allow for asynchronous functions.
Upvotes: 0