Passez prop à chaque page dans Next.js avec getInitialProps en utilisant Typescript

10

J'ai un cas où j'ai besoin de savoir si l'utilisateur est connecté ou non avant que la page ne soit rendue côté serveur et renvoyée à moi à l'aide de Next.js pour éviter d'avoir des changements de scintillement dans l'interface utilisateur.

J'ai pu comprendre comment empêcher l'utilisateur d'accéder à certaines pages s'il est déjà connecté à l'aide de ce composant 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;
};

Et cela fonctionne très bien.

Maintenant, comment puis-je créer un composant HOC similaire pour l'enrouler, disons "_app.tsx" afin que je puisse passer l'accessoire "userAuthenticated" à chaque page en obtenant le jeton et déterminer s'il est expiré ou non et basé sur cet accessoire, je peux montrer l'interface utilisateur appropriée à l'utilisateur sans cet effet de scintillement ennuyeux?

J'espère que vous pouvez m'aider avec ça, j'ai essayé de le faire de la même manière que j'ai construit le HOC ci-dessus, mais je ne pouvais pas le faire, surtout que Typescript ne rend pas cela plus facile avec ses erreurs étranges :(


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

J'ai pu créer un tel composant HOC et transmettre le pro userAuthenticatedà chaque page comme celle-ci ...

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

Cependant, j'ai dû envelopper chaque page avec ce HOC afin de transmettre l'hélice userAuthenticatedà la disposition globale que j'ai, car je ne pouvais pas envelopper le composant de classe "_app.tsx" avec, cela me donne toujours une erreur. ..

Cela marche ...

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

Mais cela ne veut pas ...

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

Il est donc un peu ennuyeux de devoir le faire pour chaque page, puis de transmettre l'accessoire à la mise en page globale dans chaque page au lieu de le faire une seule fois dans le "_app.tsx".

Je suppose que la raison peut être parce que "_app.tsx" est un composant de classe et non un composant de fonction comme le reste des pages? Je ne sais pas, je devine.

Une aide avec ça?

Rubis
la source

Réponses:

5

Pour ceux d'entre vous qui pourraient rencontrer le même problème, j'ai pu résoudre ce problème comme suit ...

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

Comme vous le voyez, j'ai publié l'intégralité du composant _app.tsx afin que vous puissiez voir les packages que j'utilise.

J'utilise next-redux-wrapperet styled-componentsavec Typescript.

J'ai dû faire le appContextin gitInitialPropsde type any, sinon cela ne fonctionnera pas. Donc, si vous avez une meilleure typesuggestion, faites-le moi savoir. J'ai essayé d'utiliser le type NextPageContext, mais cela n'a pas fonctionné dans ce cas pour une raison quelconque.

Et avec cette solution, j'ai pu savoir si l'utilisateur s'est authentifié ou non et passer un accessoire à la mise en page globale afin que je puisse l'utiliser dans chaque page et sans avoir à le faire page par page et cela profite également au cas où vous ne voulez pas que l'en-tête et le pied de page soient rendus à chaque fois s'ils doivent dépendre de l' userAuthenticatedaccessoire, car maintenant vous pouvez simplement mettre l'en-tête et le pied de page dans le GlobalLayoutcomposant et avoir toujours l' userAuthenticatedaccessoire à votre disposition: D

Rubis
la source
Mon commentaire n'est pas lié à votre message et à votre réponse. Je veux dire, ReactJSsuit la programmation fonctionnelle ni la POO, mais je vois l'état d'esprit du développement backend dans votre code avec des pensées POO. Héritez une nouvelle classe d'un autre composant! 😐 Je n'ai jamais vu ça comme ça auparavant.
AmerllicA
1
@AmerllicA Je comprends ce que vous dites, je viens de tout essayer. Le problème est avec SSR, si je travaille avec "Create React App" et que je fais du "Client-Side Rendering", je ne ferais pas ça du tout, mais je ne pourrais pas le faire fonctionner autrement avec SSR, ça est même la méthode recommandée par Next.js.
Ruby