2

I am trying to use use async in following functional component but throws error

const RouteConfig =  async ({ component: Component, fullLayout, user, auth, ...rest}) => (

  <Route
    {...rest} 
    render={props => {
      
      return (
        <ContextLayout.Consumer>
          {context => {
            let LayoutTag =
              fullLayout === true
                ? context.fullLayout
                : context.state.activeLayout === 'horizontal'
                ? context.horizontalLayout
                : context.VerticalLayout
                const verified = await verifyToken(auth.values)
            return  (auth.values !== undefined && auth.values.isSignedIn && verified) ? (
              <LayoutTag {...props} permission='{user}'>
                <Suspense fallback={<Spinner />}>
                  <Component {...props}></Component>
                </Suspense>
              </LayoutTag>
            ) : (
              <context.fullLayout {...props} permission={user}>
                <Suspense fallback={<Spinner />}>
                  <Login {...props} />
                </Suspense>
              </context.fullLayout>
            )
          }}
        </ContextLayout.Consumer>
      )
    }}
  />
)
const mapStateToProps = state => {
  return {
    user: state.auth.login.userRole,
    auth: state.auth.login
  }
}

const AppRoute = connect(mapStateToProps)(RouteConfig)

below is the verifyToken Function which return true or false

const verifyToken = async props => {
    if (props.accessToken !== undefined) {
    //if (props !== undefined) {
        if (assertAlive(jwt.decode(props.accessToken))) {
            const verified = await refreshToken(props)
            console.log(verified)
            if (verified){
                console.log('Authorized')
                return true
            } else {
                console.log('Unauthorized')
                return false
            }
        } else {
            return false
        }
    }else 
        return false
}

function assertAlive (decoded) {
    const now = Date.now().valueOf() / 1000
    if (typeof decoded.exp !== 'undefined' && decoded.exp < now) {
      //throw new Error(`token expired: ${JSON.stringify(decoded)}`)
      return false
    }
    if (typeof decoded.nbf !== 'undefined' && decoded.nbf > now) {
      //throw new Error(`token not yet valid: ${JSON.stringify(decoded)}`)
      return false
    }
    return true
  }

above used refreshToken has a functional which gets response from an API call

export const  refreshToken =  async  () => {
  const options = { withCredentials: true };
  const resp = await axios.post('http://localhost:4000/api/auth/verifyToken',{}, options).catch(err => {console.log(err); return false;});
  //console.log(resp.status)
    if (resp.status === 200){
      //console.log('200 passed')
      return true
    }else if (resp.status === 401){
      //console.log('401 failed')
      return false
    }else{
      //console.log('204 failed')
      return false
    }
}

Any suggestions would be grateful to fix this issue. If someone has better way to code this please let me know. I am bit new into ReactJS and open for suggestions. FIrst function is used to check on each route if Token is authorized or not. If its is authorized it allows to access page and if not redirects to Login Page.

After suggestions from @Mordechai, I have made below changes but it throws an error

./src/Router.js Line 193:40: React Hook "useState" is called in function "verified" which is neither a React function component or a custom React Hook function react-hooks/rules-of-hooks Line 194:3: React Hook "useEffect" is called in function "verified" which is neither a React function component or a custom React Hook function react-hooks/rules-of-hooks

function verified(auth){
  const [verified, setVerifiedValue] = useState(verifyToken(auth.values));
  useEffect(() => { setVerifiedValue(verifyToken(auth.values) )})
  return verified;
}

const RouteConfig =  ({ component: Component, fullLayout, user, auth, ...rest}) => (

  <Route
    {...rest} 
    render={props => {
      
      //useEffect(() => {const verified = verifyToken(auth.values) });
      return (
        <ContextLayout.Consumer>
          {context => {
            let LayoutTag =
              fullLayout === true
                ? context.fullLayout
                : context.state.activeLayout === 'horizontal'
                ? context.horizontalLayout
                : context.VerticalLayout
                console.log(VerifiedToken)
            return  (auth.values !== undefined && auth.values.isSignedIn && VerifiedToken) ? (
              <LayoutTag {...props} permission='{user}'>
                <Suspense fallback={<Spinner />}>
                  <Component {...props}></Component>
                </Suspense>
              </LayoutTag>
            ) : (
              <context.fullLayout {...props} permission={user}>
                <Suspense fallback={<Spinner />}>
                  <Login {...props} />
                </Suspense>
              </context.fullLayout>
            )
          }}
        </ContextLayout.Consumer>
      )
    }}
  />
)
const mapStateToProps = state => {
  return {
    user: state.auth.login.userRole,
    auth: state.auth.login
  }
}

const AppRoute = connect(mapStateToProps)(RouteConfig)
const VerifiedToken = connect(mapStateToProps)(verified)

  • Simple answer: you can't. Functional components cannot be async functions – Mordechai Jul 08 '20 at 14:00
  • Do all your async logic in `useEffect` hook – Mordechai Jul 08 '20 at 14:01
  • I tried using use Effect but not sure where to place in the component. – Prashant Gupta Jul 08 '20 at 14:02
  • 1
    Assuming you know how to use hooks, use it together with a state hook and update the state in the effect. In the rendering itself pass the state value, which can be a default value set in the state hook param. – Mordechai Jul 08 '20 at 14:04
  • If you don't know about hooks, learn that first – Mordechai Jul 08 '20 at 14:04
  • @Mordechai I have made suggested changes but not sure where i am doing mistakes. I may be sounding dumb. Please help me – Prashant Gupta Jul 08 '20 at 14:53
  • Oh, I understand where you're struggling.The hooks should be before the JSX tree. Use curly braces for your function and put the hooks and any other logic before returning the JSX. – Mordechai Jul 08 '20 at 14:58
  • @Mordechai I may sound dumb but not able to follow up the proposed solution in last comment. Is it possible you can repost the code by making changes. So I can understand and will be helpful for me from next time. – Prashant Gupta Jul 08 '20 at 18:56

2 Answers2

1

Hooks must be named useXxx otherwise eslint will complain. Also, hooks must be called on the top-level of a function component.

You call verifyToken() in both the useState() default value param and in the effect. If it's a long process you should only do it in the effect.

If you want to call verifyToken() in useVerify() just once in the lifetime of the component, you should add an empty array in the useEffect() dependency array

function useVerified(auth){
  const [verified, setVerifiedValue] = useState();
  useEffect(() => {
    const doVerify = async () => {
      setVerifiedValue(await verifyToken(auth.values))
    }
    doVerify()
  }, [])
  return verified;
}

const RouteConfig =  ({ component: Component, fullLayout, user, auth, ...rest}) => {

  const verified = useVerified(auth);

  return <Route
    {...rest} 
    render={props => {
      return (
        <ContextLayout.Consumer>
          {context => {
            let LayoutTag =
              fullLayout === true
                ? context.fullLayout
                : context.state.activeLayout === 'horizontal'
                ? context.horizontalLayout
                : context.VerticalLayout
                console.log(VerifiedToken)
            return  (auth.values !== undefined && auth.values.isSignedIn && VerifiedToken) ? (
              <LayoutTag {...props} permission='{user}'>
                <Suspense fallback={<Spinner />}>
                  <Component {...props}></Component>
                </Suspense>
              </LayoutTag>
            ) : (
              <context.fullLayout {...props} permission={user}>
                <Suspense fallback={<Spinner />}>
                  <Login {...props} />
                </Suspense>
              </context.fullLayout>
            )
          }}
        </ContextLayout.Consumer>
      )
    }}
  />
}
const mapStateToProps = state => {
  return {
    user: state.auth.login.userRole,
    auth: state.auth.login
  }
}

const AppRoute = connect(mapStateToProps)(RouteConfig)
const VerifiedToken = connect(mapStateToProps)(verified)
Mordechai
  • 13,232
  • 1
  • 32
  • 69
  • Unfortunately this also doesn't works as verified values goes every time undefined. Only thing No Syntax errors.. That's a good one. But verified value should also get read. – Prashant Gupta Jul 08 '20 at 20:14
  • I have a problem.. AppRoute getscalled on each routes in application and verifyToken verifies accessToken and changes it every time in state. To make use of it I placed "auth.values" inside empty array of useEffect but it started calling verifyToken n number of times and went in loop. Please help. – Prashant Gupta Jul 09 '20 at 01:02
  • Passing the auth.values in the array is correct. If the parent component creates the object anew for each rerender, checkout the built in hook `useMemo` to memorize the object and only change it when needed. – Mordechai Jul 09 '20 at 01:24
  • Thank for immediate reply, I have solved this problem by using path as every page gets loaded then only i need these checks and AppRoute used to get called. So i used in empty array as path. Thanks alot.. Because of you my 3 days frustration got ended. I have one more problem but i will post it as a new questions soon – Prashant Gupta Jul 09 '20 at 02:05
  • Also let me know what this means **React Hook useEffect has a missing dependency: 'auth.values'. Either include it or remove the dependency array. If 'setVerifiedValue' needs the current value of 'auth', you can also switch to useReducer instead of useState and read 'auth.values' in the reducer** – Prashant Gupta Jul 09 '20 at 02:14
  • No problem, you can also upvote this answer. The warning can safely be suppressed by the comment suggested by the IDE. It is meant to warn for potential missing dependencies, but since you only want it once you can suppress it. – Mordechai Jul 09 '20 at 02:22
  • i already upvoted the answer but seems like i don't have 15 reputation which is why its not publicly visible. I am at 13 repo and will upvote once reaches 15.. Thanks for wonderful help and time. – Prashant Gupta Jul 09 '20 at 03:03
0

Does the following work?

const NONE = {};
const RouteConfig = ({
  component: Component,
  fullLayout,
  user,
  auth,
  ...rest
}) => (
  <Route
    {...rest}
    render={(props) => {
      const [verified, setVerified] = React.useState(NONE);
      React.useEffect(
        () => verifyToken(auth.values).then(setVerified),
        []
      );
      if (verified === NONE) {
        return null;
      }
      return (
        <ContextLayout.Consumer>
          {(context) => {
            let LayoutTag =
              fullLayout === true
                ? context.fullLayout
                : context.state.activeLayout ===
                  'horizontal'
                ? context.horizontalLayout
                : context.VerticalLayout;
            return auth.values !== undefined &&
              auth.values.isSignedIn &&
              verified ? (
              <LayoutTag {...props} permission="{user}">
                <Suspense fallback={<Spinner />}>
                  <Component {...props}></Component>
                </Suspense>
              </LayoutTag>
            ) : (
              <context.fullLayout
                {...props}
                permission={user}
              >
                <Suspense fallback={<Spinner />}>
                  <Login {...props} />
                </Suspense>
              </context.fullLayout>
            );
          }}
        </ContextLayout.Consumer>
      );
    }}
  />
);
HMR
  • 30,349
  • 16
  • 67
  • 136
  • No it still throws an error ./src/Router.js Line 198:39: React Hook "React.useState" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks Line 199:7: React Hook "React.useEffect" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks – Prashant Gupta Jul 08 '20 at 18:17