Search code examples
reactjstypescripttypesreact-context

'AuthProvider' cannot be used as a JSX component


I attempt to make auth flow in Context Provider and have troubles with TypeScript. Here is problem fragment of Provider code:

interface AuthUserProviderProps {
  children?: React.ReactNode 
}

interface AuthUserInterface {
  token: string;
  setToken: (value: string) => void;
  loggedIn: boolean;
  };
  setBrand: (value: {}) => void;
}

export const authUser = createContext<AuthUserInterface | null >(null);

const AuthUserProvider = ({ children }: AuthUserProviderProps) => {

if (accessTokenVAR() && !jwtToken?.roles.toString().includes('admin')) {
  return isBrowser ? window.location.replace('www.somewhere.com') : ''
}

  return (
    <authUser.Provider
      value={{
        token,
        setToken,
        loggedIn,
      }}
    >
      { children}
    </authUser.Provider>
  );
};

export default AuthUserProvider;

App.tsx

export default function App({ Component, pageProps }: AppProps) {
  const apolloClient = useApollo(pageProps.initialApolloState);
  return (
    <ThemeProvider theme={TifTheme}>
      <AuthUserProvider>                      <===This line throws error
        <ApolloProvider client={apolloClient} >
          <CssBaseline />
          <Component {...pageProps} />
        </ApolloProvider>
      </AuthUserProvider>
    </ThemeProvider>
  );
}

The error is 'AuthUserProvider' cannot be used as a JSX component. Its return type 'void | "" | Element' is not a valid JSX element. Type 'void' is not assignable to type 'Element.' and it is caused by return redirect. But I have no clue how to handle this error


Solution

  • Your analysis should be correct, it's the return of the "redirect". You can easily type properties of React functional components with React.FunctionComponent.

    I would recommend to write a little useAccess hook and use that to get a boolean for display reasons or redirect the user with a useEffect. As you redirect to a different page, it shouldn't matter what the component returns. So I modified your code to this, I made some changes and added comments, let me know if it helps.

    import { createContext, FunctionComponent, useEffect, useMemo } from "react";
    
    interface AuthUserInterface {
      token: string;
      setToken: (value: string) => void;
      loggedIn: boolean;
      setBrand: (value: any) => void;
    }
    
    const AuthUser = createContext<AuthUserInterface | null>(null);
    
    const useAccess = (isBrowser: boolean) => {
      const hasAccess = useMemo(
        () => accessTokenVAR() && !jwtToken?.roles.toString().includes("admin"),
        // TODO: check if that's correct, these variables were not in your answer,
        // might need to add/pass them to the hook
        [accessTokenVAR, jwtToken]
      );
    
      useEffect(() => {
        // redirect here if has
        if (hasAccess && isBrowser) window.location.replace("www.somewhere.com");
      }, [isBrowser, hasAccess]);
    
      return hasAccess;
    };
    
    interface AuthUserProviderProps {
      isBrowser: boolean;
    }
    
    const AuthUserProvider: FunctionComponent<AuthUserProviderProps> = ({
      children,
      isBrowser
    }) => {
      const hasAccess = useAccess(isBrowser);
    
      return (
        <>
          {!hasAccess && <p>"No access"</p>}
          {hasAccess && (
            <AuthUser.Provider
              value={{
                // TODO: values not in question, where do they come from
                token,
                setToken,
                loggedIn
              }}
            >
              {children}
            </AuthUser.Provider>
          )}
        </>
      );
    };
    
    export { AuthUserProvider as default, AuthUser };