Przekaż prop do każdej strony w Next.js za pomocą getInitialProps za pomocą Typescript

10

Mam przypadek, gdy muszę wiedzieć, czy użytkownik jest zalogowany, czy nie, zanim strona zostanie renderowana po stronie serwera i odesłana do mnie za pomocą Next.js, aby uniknąć migotania zmian w interfejsie użytkownika.

Byłem w stanie dowiedzieć się, jak uniemożliwić użytkownikowi dostęp do niektórych stron, jeśli jest on już zalogowany przy użyciu tego komponentu 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;
};

I to działa świetnie.

Teraz, jak mogę zbudować podobny komponent HOC, aby go owinąć, powiedzmy „_app.tsx”, aby móc przekazać propozycję „UserAuthentified” na każdej stronie, pobierając token i stwierdzając, czy wygasł, czy nie i na podstawie ten rekwizyt mogę pokazać użytkownikowi odpowiedni interfejs bez tego irytującego efektu migotania?

Mam nadzieję, że możesz mi w tym pomóc. Próbowałem to zrobić w ten sam sposób, w jaki zbudowałem powyższy HOC, ale nie mogłem tego zrobić, tym bardziej, że maszynopis nie ułatwia tego z powodu swoich dziwnych błędów :(


Edytuj == ==========================================

Byłem w stanie stworzyć taki komponent HOC i przekazać pro userAuthenticateddo każdej strony w ten sposób ...

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;
};

Jednak musiałem zawinąć każdą stronę za pomocą tego HOC, aby przekazać prop userAuthenticateddo globalnego układu, który posiadam, ponieważ nie mogłem go owinąć komponentem klasy „_app.tsx”, zawsze daje mi to błąd. ..

To działa ...

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

Ale to nie ...

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

To trochę denerwujące, że trzeba to zrobić na każdej stronie, a następnie przekazać prop do globalnego układu na każdej stronie zamiast po prostu zrobić to raz w „_app.tsx”.

Zgaduję, że powodem może być to, że „_app.tsx” jest składnikiem klasy, a nie składnikiem funkcji takim jak reszta stron? Nie wiem, tylko zgaduję.

Jakaś pomoc w tym?

Rubin
źródło

Odpowiedzi:

5

Dla tych z was, którzy mogą napotkać ten sam problem, udało mi się rozwiązać ten problem w następujący sposób ...

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);

Jak widzisz, opublikowałem cały komponent _app.tsx, abyś mógł zobaczyć pakiety, których używam.

Używam next-redux-wrapperi styled-componentsz maszynopisu.

Musiałem dokonać appContextin gitInitialPropsjako typu any, inaczej nie zadziała. Więc jeśli masz lepszą typesugestię, daj mi znać. Próbowałem użyć tego typu NextPageContext, ale z jakiegoś powodu to nie zadziałało w tym przypadku.

Dzięki temu rozwiązaniu mogłem dowiedzieć się, czy użytkownik został uwierzytelniony, czy też nie, i przekazać prop do globalnego układu, dzięki czemu mogę go używać na każdej stronie i bez konieczności robienia tego na stronie, co również przynosi korzyści w przypadku nie chcesz, aby nagłówek i stopka były renderowane za każdym razem, jeśli muszą zależeć od userAuthenticatedrekwizytu, ponieważ teraz możesz po prostu umieścić nagłówek i stopkę w GlobalLayoutkomponencie i nadal mieć do userAuthenticateddyspozycji rekwizyt: D

Rubin
źródło
Mój komentarz nie jest związany z Twoim postem i odpowiedzią. Chcę powiedzieć, ReactJSpodąża za programowaniem funkcjonalnym ani OOP, ale widzę sposób myślenia o rozwoju zaplecza w twoim kodzie z myślami OOP. Dziedzicz nową klasę po innym elemencie! Before Nie widziałem tak wcześniej.
AmerllicA
1
@AmerllicA Rozumiem, co mówisz, właśnie próbowałem wszystkiego. Problem dotyczy SSR, jeśli pracuję z „Utwórz aplikację React” i wykonuję „Renderowanie po stronie klienta”, w ogóle bym tego nie zrobił, ale nie mogłem go uruchomić w inny sposób z SSR, jest nawet zalecanym sposobem Next.js.
Ruby