Передайте prop каждой странице в Next.js с помощью getInitialProps, используя Typescript

10

У меня есть случай, когда мне нужно знать, вошел ли пользователь в систему или нет, прежде чем страница будет обработана на стороне сервера и отправлена ​​мне обратно с помощью Next.js, чтобы избежать мерцания изменений в пользовательском интерфейсе.

Мне удалось выяснить, как запретить пользователю доступ к некоторым страницам, если он уже вошел в систему с использованием этого компонента HOC ...

export const noAuthenticatedAllowed = (WrappedComponent: NextPage) => {
    const Wrapper = (props: any) => {
        return <WrappedComponent {...props} />;
    };

    Wrapper.getInitialProps = async (ctx: NextPageContext) => {
        let context = {};
        const { AppToken } = nextCookie(ctx);
        if (AppToken) {
            const decodedToken: MetadataObj = jwt_decode(AppToken);
            const isExpired = () => {
                if (decodedToken.exp < Date.now() / 1000) {
                    return true;
                } else {
                    return false;
                }
            };

            if (ctx.req) {
                if (!isExpired()) {
                    ctx.res && ctx.res.writeHead(302, { Location: "/" });
                    ctx.res && ctx.res.end();
                }
            }

            if (!isExpired()) {
                context = { ...ctx };
                Router.push("/");
            }
        }

        const componentProps =
            WrappedComponent.getInitialProps &&
            (await WrappedComponent.getInitialProps(ctx));

        return { ...componentProps, context };
    };

    return Wrapper;
};

И это прекрасно работает.

Теперь, как я могу создать аналогичный компонент HOC, чтобы обернуть его, скажем, «_app.tsx», чтобы я мог передавать «userAuthenticated» реквизит каждой отдельной странице, получая токен и выясняя, истек ли он или нет, и основываясь на что я могу показать пользователю правильный интерфейс без этого раздражающего мерцающего эффекта?

Я надеюсь, что вы можете помочь мне с этим, я пытался сделать это так же, как я построил вышеупомянутый HOC, но я не мог сделать это, тем более что Typescript не облегчает это с его странными ошибками :(


Edit == ==========================================

Я смог создать такой компонент HOC и передать про userAuthenticatedна каждой странице, как это ...

export const isAuthenticated = (WrappedComponent: NextPage) => {
    const Wrapper = (props: any) => {
        return <WrappedComponent {...props} />;
    };

    Wrapper.getInitialProps = async (ctx: NextPageContext) => {
        let userAuthenticated = false;

        const { AppToken} = nextCookie(ctx);
        if (AppToken) {
            const decodedToken: MetadataObj = jwt_decode(AppToken);
            const isExpired = () => {
                if (decodedToken.exp < Date.now() / 1000) {
                    return true;
                } else {
                    return false;
                }
            };

            if (ctx.req) {
                if (!isExpired()) {
                    // ctx.res && ctx.res.writeHead(302, { Location: "/" });
                    // ctx.res && ctx.res.end();
                    userAuthenticated = true;
                }
            }

            if (!isExpired()) {
                userAuthenticated = true;
            }
        }

        const componentProps =
            WrappedComponent.getInitialProps &&
            (await WrappedComponent.getInitialProps(ctx));

        return { ...componentProps, userAuthenticated };
    };

    return Wrapper;
};

Однако мне пришлось обернуть каждую страницу с помощью этого HOC, чтобы передать реквизит userAuthenticatedимеющемуся у меня глобальному макету, потому что я не смог обернуть его компонентом класса «_app.tsx», он всегда выдает ошибку. ..

Это работает ...

export default isAuthenticated(Home);
export default isAuthenticated(about);

Но это не ...

export default withRedux(configureStore)(isAuthenticated(MyApp));

Так что немного раздражает необходимость делать это на каждой странице, а затем передавать информацию о глобальном макете на каждой странице вместо того, чтобы просто сделать это один раз в «_app.tsx».

Я предполагаю, что причина может быть в том, что "_app.tsx" является компонентом класса, а не компонентом функции, как остальные страницы? Я не знаю, я просто догадываюсь.

Любая помощь с этим?

Рубин
источник

Ответы:

5

Для тех из вас, кто может столкнуться с той же проблемой, я смог решить эту проблему следующим образом ...

import React from "react";
import App from "next/app";
import { Store } from "redux";
import { Provider } from "react-redux";
import withRedux from "next-redux-wrapper";
import { ThemeProvider } from "styled-components";
import GlobalLayout from "../components/layout/GlobalLayout";
import { configureStore } from "../store/configureStore";
import { GlobalStyle } from "../styles/global";
import { ToastifyStyle } from "../styles/toastify";
import nextCookie from "next-cookies";
import jwt_decode from "jwt-decode";

 export interface MetadataObj {
   [key: string]: any;
 }

const theme = {
    color1: "#00CC99",
    color2: "#CC0000"
};

export type ThemeType = typeof theme;

interface Iprops {
    store: Store;
    userAuthenticated: boolean;
}

class MyApp extends App<Iprops> {
    // Only uncomment this method if you have blocking data requirements for
    // every single page in your application. This disables the ability to
    // perform automatic static optimization, causing every page in your app to
    // be server-side rendered.

    static async getInitialProps({ Component, ctx }: any) {
        let userAuthenticated = false;

        const { AppToken } = nextCookie(ctx);
        if (AppToken) {
            const decodedToken: MetadataObj = jwt_decode(AppToken);
            const isExpired = () => {
                if (decodedToken.exp < Date.now() / 1000) {
                    return true;
                } else {
                    return false;
                }
            };

            if (ctx.isServer) {
                if (!isExpired()) {
                    userAuthenticated = true;
                }
            }

            if (!isExpired()) {
                userAuthenticated = true;
            }
        }

        return {
            pageProps: Component.getInitialProps
                ? await Component.getInitialProps(ctx)
                : {},
            userAuthenticated: userAuthenticated
        };
    }

    render() {
        const { Component, pageProps, store, userAuthenticated } = this.props;
        return (
            <Provider store={store}>
                <ThemeProvider theme={theme}>
                    <>
                        <GlobalStyle />
                        <ToastifyStyle />
                        <GlobalLayout userAuthenticated={userAuthenticated}>
                            <Component {...pageProps} />
                        </GlobalLayout>
                    </>
                </ThemeProvider>
            </Provider>
        );
    }
}

export default withRedux(configureStore)(MyApp);

Как вы видите, я разместил весь компонент _app.tsx, чтобы вы могли видеть пакеты, которые я использую.

Я использую next-redux-wrapperи styled-componentsс Typescript.

Я должен был сделать appContextпо прибытию в gitInitialPropsкачестве типа any, в противном случае он не будет работать. Так что, если у вас есть лучшее typeпредложение, пожалуйста, дайте мне знать. Я пытался использовать тип NextPageContext, но по какой-то причине это не сработало.

И благодаря этому решению я смог узнать, прошел ли пользователь проверку подлинности или нет, и передать опору глобальному макету, чтобы я мог использовать его на каждой странице без необходимости делать это на каждой странице, что также полезно в случае Вы не хотите, чтобы верхний и нижний колонтитулы отображались каждый раз, когда они должны зависеть от userAuthenticatedупора, потому что теперь вы можете просто поместить верхний и нижний колонтитулы в GlobalLayoutкомпонент и по-прежнему иметь userAuthenticatedв своем распоряжении подпорку: D

Рубин
источник
Мой комментарий не связан с вашим постом и вашим ответом. Я хочу сказать, ReactJSследует функциональному программированию или ООП, но я вижу образ мышления бэкэнда в вашем коде с мыслями ООП. Унаследуйте новый класс от другого компонента! «Я не видел, как это раньше.
AmerllicA
1
@AmerllicA Я понимаю, что вы говорите, я просто попробовал все. Проблема связана с SSR, если я работаю с «Создать приложение React» и выполняю «Рендеринг на стороне клиента», я бы вообще этого не делал, но я не мог заставить его работать по-другому с SSR. это даже рекомендуемый путь Next.js.
Рубин