user866364
user866364

Reputation:

PrivateRoute - how to wait a response from async?

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

Answers (4)

Shubham Khatri
Shubham Khatri

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

A. Ecrubit
A. Ecrubit

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

snaksa
snaksa

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

Robert Cooper
Robert Cooper

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

Related Questions