Passe prop para cada página do Next.js com getInitialProps usando o Typecript

10

Tenho um caso em que preciso saber se o usuário está logado ou não antes que a página seja renderizada no servidor e enviada de volta para mim usando o Next.js para evitar alterações de cintilação na interface do usuário.

Consegui descobrir como impedir o usuário de acessar algumas páginas se ele já estiver logado usando este 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 isso funciona muito bem.

Agora, como posso criar um componente HOC semelhante para envolvê-lo, digamos "_app.tsx" para que eu possa passar o propulsor "userAuthenticated" para cada página, obtendo o token e descobrindo se ele expirou ou não e com base em esse suporte posso mostrar a interface do usuário adequada ao usuário sem esse efeito irritante de cintilação?

Espero que você possa me ajudar com isso, tentei fazer da mesma maneira que construí o HOC acima, mas não consegui, principalmente porque o Typescript não facilita as coisas com seus erros estranhos :(


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

Consegui criar esse componente HOC e passar o profissional userAuthenticatedpara cada página como esta ...

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

No entanto, eu tive que quebrar todas as páginas com esse HOC para passar o suporte userAuthenticatedao layout global que eu tenho, porque não conseguia envolver o componente de classe "_app.tsx" com ele, isso sempre me dá um erro. ..

Isso funciona ...

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

Mas isso não ...

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

Portanto, é um pouco chato ter que fazer isso em todas as páginas e depois passar o suporte para o layout global em todas as páginas, em vez de fazê-lo apenas uma vez no "_app.tsx".

Eu estou supondo que o motivo pode ser porque "_app.tsx" é um componente de classe e não um componente de função como o restante das páginas? Eu não sei, estou apenas adivinhando.

Alguma ajuda com isso?

Rubi
fonte

Respostas:

5

Para aqueles de vocês que podem encontrar o mesmo problema, resolvi isso da seguinte maneira ...

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

Como você vê, publiquei todo o componente _app.tsx para que você possa ver os pacotes que estou usando.

Estou usando next-redux-wrappere styled-componentscom texto datilografado.

Eu tive que fazer o appContextem gitInitialPropscomo do tipo any, caso contrário não vai funcionar. Então, se você tiver uma typesugestão melhor, por favor me avise. Eu tentei usar o tipo NextPageContext, mas isso não funcionou neste caso por algum motivo.

E com essa solução, fui capaz de saber se o usuário estava autenticado ou não e passar um suporte para o layout global, para que eu possa usá-lo em todas as páginas e sem precisar fazê-lo página por página, o que também se beneficia. você não deseja que o cabeçalho e o rodapé sejam renderizados sempre que tiverem que depender do userAuthenticatedsuporte, porque agora você pode simplesmente colocar o cabeçalho e o rodapé no GlobalLayoutcomponente e ainda ter o userAuthenticatedsuporte à sua disposição: D

Rubi
fonte
Meu comentário não está relacionado à sua postagem e sua resposta. Quero dizer, ReactJSsegue a Programação Funcional nem o POO, mas vejo a mentalidade do desenvolvimento de back-end no código com pensamentos OOP. Herdar uma nova classe de outro componente! “Eu não vi como antes.
AmerllicA
11
@AmerllicA Eu entendo o que você está dizendo, eu apenas tentei de tudo. O problema é com o SSR, se eu estiver trabalhando com o "Create React App" e fazendo "Client-Side Rendering", eu não faria isso, mas não poderia fazê-lo funcionar de outra maneira com o SSR. é até a maneira recomendada pelo Next.js.
Ruby