Pase el accesorio a cada página en Next.js con getInitialProps usando Typecript

10

Tengo un caso en el que necesito saber si el usuario ha iniciado sesión o no antes de que la página se muestre en el lado del servidor y me la envíe de nuevo usando Next.js para evitar cambios de parpadeo en la interfaz de usuario.

Pude descubrir cómo evitar que el usuario acceda a algunas páginas si ya inició sesión con 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;
};

Y esto funciona muy bien.

Ahora, ¿cómo puedo construir un componente HOC similar para envolverlo? Digamos "_app.tsx" para que pueda pasar el accesorio "UserAuthenticated" a cada página obteniendo el token y averiguar si ha caducado o no y en función de ¿con qué accesorio puedo mostrar la IU adecuada al usuario sin ese molesto efecto de parpadeo?

Espero que me puedan ayudar con eso, traté de hacerlo de la misma manera que construí el HOC anterior, pero no pude hacerlo, especialmente porque TypeScript no lo hace más fácil con sus errores extraños :(


Editar == ==========================================

Pude crear dicho componente HOC y pasar el pro userAuthenticateda cada página de esta manera ...

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

Sin embargo, tuve que ajustar cada página con este HOC para pasar el accesorio userAuthenticatedal diseño global que tengo, porque no pude envolver el componente de clase "_app.tsx" con él, siempre me da un error. ..

Esto funciona ...

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

Pero esto no ...

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

Por lo tanto, es un poco molesto tener que hacer esto en todas y cada una de las páginas, y luego pasar el accesorio al diseño global en todas y cada una de las páginas en lugar de hacerlo una vez en el "_app.tsx".

Supongo que la razón puede ser porque "_app.tsx" es un componente de clase y no un componente de función como el resto de las páginas. No sé, solo estoy adivinando.

¿Alguna ayuda con eso?

Rubí
fuente

Respuestas:

5

Para aquellos de ustedes que podrían encontrar el mismo problema, pude resolver esto de la siguiente manera ...

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 puede ver, publiqué todo el componente _app.tsx para que pueda ver los paquetes que estoy usando.

Estoy usando next-redux-wrappery styled-componentscon Typecript.

Tuve que hacer el appContexten gitInitialPropscomo de tipo any, de lo contrario no funcionará. Entonces, si tiene una typesugerencia mejor , hágamelo saber. Traté de usar el tipo NextPageContext, pero eso no funcionó en este caso por alguna razón.

Y con esa solución, pude saber si el usuario se autenticó o no y pasar un accesorio al diseño global para que pueda usarlo en cada página y sin tener que hacerlo página por página y eso también se beneficia en caso de que no desea que el encabezado y el pie de página se muestren cada vez si tienen que depender del userAuthenticatedaccesorio, porque ahora solo puede poner el encabezado y el pie de página en el GlobalLayoutcomponente y aún tener el userAuthenticatedaccesorio a su disposición: D

Rubí
fuente
Mi comentario no está relacionado con tu publicación y tu respuesta. Quiero decir, ReactJSsigue la Programación Funcional ni OOP, pero veo la mentalidad del desarrollo del backend en tu código con pensamientos de OOP. ¡Herede una nueva clase de otro componente! Didn't No lo vi así antes.
AmerllicA
1
@AmerllicA Entiendo lo que dices, acabo de intentarlo todo. El problema es con SSR, si estoy trabajando con "Crear aplicación React" y haciendo "Representación del lado del cliente" no haría eso en absoluto, pero no podría hacer que funcione de otra manera con SSR, es incluso la forma recomendada por Next.js.
Ruby