Passa prop a ogni singola pagina in Next.js con getInitialProps usando Typescript


10

Ho un caso in cui ho bisogno di sapere se l'utente ha effettuato l'accesso o meno prima che la pagina venga renderizzata sul lato server e rispedita a me usando Next.js per evitare modifiche dello sfarfallio nell'interfaccia utente.

Sono stato in grado di capire come impedire all'utente di accedere ad alcune pagine se ha già effettuato l'accesso utilizzando questo componente 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;
};

E questo funziona alla grande.

Ora, come posso costruire un componente HOC simile per avvolgerlo diciamo "_app.tsx" in modo da poter passare il prop "userAuthenticated" a ogni singola pagina ottenendo il token e capire se è scaduto o meno e basato su che prop posso mostrare all'utente l'interfaccia utente corretta senza quel fastidioso effetto di sfarfallio?

Spero che tu mi possa aiutare in questo, ho provato a farlo nello stesso modo in cui ho creato il HOC sopra, ma non ci sono riuscito, specialmente quel Typescript non lo rende più facile con i suoi strani errori :(


Modifica == ==========================================

Sono stato in grado di creare tale componente HOC e passare il pro userAuthenticatedad ogni pagina in questo modo ...

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

Tuttavia, ho dovuto avvolgere ogni singola pagina con questo HOC al fine di tramandare il userAuthenticatedlayout al layout globale che ho, perché non ho potuto avvolgere il componente di classe "_app.tsx" con esso, mi dà sempre un errore. ..

Questo funziona ...

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

Ma questo non ...

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

Quindi è un po 'fastidioso dover fare questo per ogni singola pagina, e poi passare il prop al layout globale in ogni singola pagina invece di farlo una sola volta in "_app.tsx".

Immagino che il motivo potrebbe essere perché "_app.tsx" è un componente di classe e non un componente di funzione come il resto delle pagine? Non lo so, sto solo indovinando.

Qualche aiuto con quello?

Risposte:


5

Per quelli di voi che potrebbero imbattersi nello stesso problema, sono stato in grado di risolvere questo problema come segue ...

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

Come vedi ho pubblicato l'intero componente _app.tsx in modo che tu possa vedere i pacchetti che sto usando.

Sto usando next-redux-wrappere styled-componentscon Typescript.

Ho dovuto fare l' appContextin gitInitialPropsquanto di tipo any, altrimenti non funzionerà. Quindi, se hai un typesuggerimento migliore , faccelo sapere. Ho provato a usare il tipo NextPageContext, ma in questo caso non ha funzionato per qualche motivo.

E con quella soluzione, sono stato in grado di sapere se l'utente si è autenticato o meno e passare un oggetto di scena al layout globale in modo da poterlo utilizzare in ogni pagina e senza doverlo fare pagina per pagina e che benefici anche nel caso non vuoi che l'intestazione e il piè di pagina vengano resi ogni volta se devono dipendere userAuthenticateddall'elica, perché ora puoi semplicemente mettere l'intestazione e il piè di pagina nel GlobalLayoutcomponente e avere ancora l' userAuthenticatedelica a tua disposizione: D


Il mio commento non è correlato al tuo post e alla tua risposta. Voglio dire, ReactJSsegue la Programmazione Funzionale né OOP, ma vedo la mentalità dello sviluppo del backend nel tuo codice con pensieri OOP. Eredita una nuova classe da un altro componente! 😐Non l'avevo mai visto prima.
Amerllic, il

1
@AmerllicA Ho capito cosa stai dicendo, ho appena provato di tutto. Il problema è con SSR, se sto lavorando con "Crea app React" e sto facendo "Rendering lato client" Non lo farei affatto, ma non potrei farlo funzionare diversamente con SSR, è anche il modo raccomandato da Next.js.
Ruby,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.