简体   繁体   中英

ReactJs, Typescript protected route with HOC as functional component

I can't figure out how to build a simple HOC to protect a component, ie make sure that the user is logged in before the component renders.

Here is my attempt to build the HOC to protect the component (doesn't work yet).

export default function ProtectedRoute(Component: React.ComponentType) {
  return () => {
    const authUser = useSession();
    const router = useRouter();

    if (typeof window === 'undefined') {
      return null;
    }

    if (!authUser) {
      router.push('/login');
      return null;
    }
    return <Component />;
  };
}

Here is the component I want to protect.

const AppLayout = ({
  children,
  title = 'This is the default title',
}: Props): React.ReactElement => {
return (
     ...
      <main className="bg-nord-bg2">
        <div className="min-h-screen w-full max-w-screen-xl mx-auto py-6 sm:px-6 lg:px-8">
          <Alert />
          {children}
        </div>
      </main>
)
}

export default ProtectedRoute(AppLayout);

This is how I call the layout component:

function App({ Component, pageProps }: any) {
  return (
        <Layout title="Meno">
          <Component {...pageProps} />
        </Layout>
      ) 
}

export default App;

This is my error message: Argument of type '({ children, title, }: Props) => React.ReactElement' is not assignable to parameter of type 'ComponentType<{}>'. Type '({ children, title, }: Props) => React.ReactElement' is not assignable to type 'FunctionComponent<{}>'. Types of parameters '__0' and 'props' are incompatible. Property 'title' is missing in type '{ children?: ReactNode; }' but required in type 'Props' Argument of type '({ children, title, }: Props) => React.ReactElement' is not assignable to parameter of type 'ComponentType<{}>'. Type '({ children, title, }: Props) => React.ReactElement' is not assignable to type 'FunctionComponent<{}>'. Types of parameters '__0' and 'props' are incompatible. Property 'title' is missing in type '{ children?: ReactNode; }' but required in type 'Props'

Do you have any hints for me?

In short, your AppLayout doesn't return a ReactElement but a FC (Functional Component).

I'd recommend this cheatsheat for type differences.


Here's a working example project that can be found here .

components/AppContext/index.tsx

import { createContext, useCallback, useContext, useState } from "react";
import type { FC, ReactNode } from "react";

type Context = {
  isAuthenticated: Boolean;
  handleAuthentication: () => void;
}

const AppContext = createContext<Context>({
  isAuthenticated: false,
  handleAuthentication: () => {},
});

const AppContextProvider: FC<{ children: ReactNode }> = ({ children }) => {
  const [isAuthenticated, setAuthentication] = useState(false);

  const handleAuthentication = useCallback(() => {
    setAuthentication((prevState) => !prevState);
  }, []);

  return (
    <AppContext.Provider
      value={{
        isAuthenticated,
        handleAuthentication,
      }}
    >
      {children}
      {isAuthenticated && (
        <button type="button" onClick={handleAuthentication}>
          Log out
        </button>
      )}
    </AppContext.Provider>
  );
};

export const useAppContext = () => useContext(AppContext);

export default AppContextProvider;

components/withAuthentication/index.tsx

import { useRouter } from "next/router";
import { useEffect } from "react";
import { useAppContext } from "../AppContext";
import type { FC } from "react";

type withAuthenticationFn = (Component: FC) => FC;

const withAuthentication: withAuthenticationFn = (Component) => {
  const Authenticated: FC = (): JSX.Element | null => {
    const { isAuthenticated } = useAppContext();
    const router = useRouter();

    useEffect(() => {
      if (!isAuthenticated) router.push("/login");
    });

    return isAuthenticated ? <Component /> : null;
  };

  return Authenticated;
};

export default withAuthentication;

pages/_app.tsx

import AppContextProvider from "../components/AppContext";
import type { FC } from "react";
import type { AppProps } from "next/app";

const App: FC<AppProps> = ({ Component, pageProps }) => (
  <AppContextProvider>
    <Component {...pageProps} />
  </AppContextProvider>
);

export default App;

pages/about.tsx

import Head from "next/head";
import Link from "next/link";
import withAuthentication from "../components/withAuthentication";
import type { NextPage } from "next";

const AboutPage: NextPage = () => (
  <>
    <Head>
      <title> About - Next App</title>
    </Head>
    <h1>About</h1>
    <p>This is the about page</p>
    <p>
      <Link href="/">
        <a>Go home</a>
      </Link>
    </p>
  </>
);

export default withAuthentication(AboutPage);

pages/index.tsx

import Link from "next/link";
import Head from "next/head";
import withAuthentication from "../components/withAuthentication";
import type { NextPage } from "next";

const IndexPage: NextPage = () => (
  <>
    <Head>
      <title> Dashboard - Next App</title>
    </Head>
    <h1>Hello Next.js 👋</h1>
    <p>
      <Link href="/about">
        <a>About</a>
      </Link>
    </p>
  </>
);

export default withAuthentication(IndexPage);

pages/login.tsx

import Head from "next/head";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useAppContext } from "../components/AppContext";
import type { NextPage } from "next";

const Login: NextPage = () => {
  const { isAuthenticated, handleAuthentication } = useAppContext();
  const router = useRouter();

  useEffect(() => {
    if (isAuthenticated) router.push("/");
  }, [isAuthenticated]);

  return (
    <>
      <Head>
        <title> Login - Next App</title>
      </Head>
      <button type="button" onClick={handleAuthentication}>
        Log in
      </button>
    </>
  );
};

export default Login;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM