3

I am encountering a browser infinite looping issue in my NextJS project when I add the SSRKeycloakProvider component from @react-keycloak/ssr npm package. This infinite loop only happens with a specific application the Reports page.

My investigations have led me to believe it is related to the way cookies are handled with keycloak integration, and in the Reports page I am also using windows.replaceState() javascript function.

Everytime the loop starts, this is the message I get from the application:

[Function: setReportq]
Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://reactjs.org/link/uselayouteffect-ssr for common fixes.
at Overflow (/home/ubuntu/growth-admin/node_modules/rc-overflow/lib/Overflow.js:42:32)
at SelectSelector (/home/ubuntu/growth-admin/node_modules/rc-select/lib/Selector/MultipleSelector.js:36:18)
at div
at Selector (/home/ubuntu/growth-admin/node_modules/rc-select/lib/Selector/index.js:38:35)
at Trigger (/home/ubuntu/growth-admin/node_modules/rc-trigger/lib/index.js:79:36)
at SelectTrigger (/home/ubuntu/growth-admin/node_modules/rc-select/lib/SelectTrigger.js:74:25)
at div
at BaseSelect (/home/ubuntu/growth-admin/node_modules/rc-select/lib/BaseSelect.js:67:18)
at Select (/home/ubuntu/growth-admin/node_modules/rc-select/lib/Select.js:66:18)
at InternalSelect (/home/ubuntu/growth-admin/node_modules/antd/lib/select/index.js:55:31)
at div
at div
at div
at Col (/home/ubuntu/growth-admin/node_modules/antd/lib/grid/col.js:59:33)
at FormItemInput (/home/ubuntu/growth-admin/node_modules/antd/lib/form/FormItemInput.js:44:25)
at div
at Row (/home/ubuntu/growth-admin/node_modules/antd/lib/grid/row.js:56:34)
at FormItem (/home/ubuntu/growth-admin/node_modules/antd/lib/form/FormItem.js:101:20)
at form
at Form (/home/ubuntu/growth-admin/node_modules/rc-field-form/lib/Form.js:33:19)
at SizeContextProvider (/home/ubuntu/growth-admin/node_modules/antd/lib/config-provider/SizeContext.js:19:23)
at InternalForm (/home/ubuntu/growth-admin/node_modules/antd/lib/form/Form.js:66:27)
at div
at Report (webpack-internal:///./common/components/DominoReport/index.js:152:3)
at div
at appClassicstyle__ContentWrapper (/home/ubuntu/growth-admin/node_modules/styled-components/dist/styled-components.cjs.js:1:19220)
at div
at appClassicstyle__AppWrapper (/home/ubuntu/growth-admin/node_modules/styled-components/dist/styled-components.cjs.js:1:19220)
at AppClassic (webpack-internal:///./common/components/Admin/report.js:27:3)
at div
at main
at Basic (/home/ubuntu/growth-admin/node_modules/antd/lib/layout/layout.js:78:25)
at Content (/home/ubuntu/growth-admin/node_modules/antd/lib/layout/layout.js:61:37)
at section
at BasicLayout (/home/ubuntu/growth-admin/node_modules/antd/lib/layout/layout.js:93:34)
at Layout (/home/ubuntu/growth-admin/node_modules/antd/lib/layout/layout.js:61:37)
at section
at BasicLayout (/home/ubuntu/growth-admin/node_modules/antd/lib/layout/layout.js:93:34)
at Layout (/home/ubuntu/growth-admin/node_modules/antd/lib/layout/layout.js:61:37)
at exports.ThemeProvider (/home/ubuntu/growth-admin/node_modules/styled-components/dist/styled-components.cjs.js:1:24917)
at Growth (webpack-internal:///./containers/Admin/growth.js:81:3)
at AppClassic (webpack-internal:///./pages/report.js:19:3)
at ThemeProvider (/home/ubuntu/growth-admin/node_modules/@material-ui/styles/ThemeProvider/ThemeProvider.js:48:24)
at KeycloakProvider (/home/ubuntu/growth-admin/node_modules/@react-keycloak/core/lib-commonjs/provider.js:72:51)
at SSRKeycloakProvider (/home/ubuntu/growth-admin/node_modules/@react-keycloak/ssr/lib-commonjs/SSRKeycloakProvider.js:64:28)
at CustomApp (webpack-internal:///./pages/_app.tsx:63:3)
at StylesProvider (/home/ubuntu/growth-admin/node_modules/@material-ui/styles/StylesProvider/StylesProvider.js:57:24)
at ae (/home/ubuntu/growth-admin/node_modules/styled-components/dist/styled-components.cjs.js:1:13296)
at AppContainer (/home/ubuntu/growth-admin/node_modules/next/dist/server/render.js:293:29)

The above message mentions the parseCookies function (in _app.tsx:63:3) , the pages/index.tsx line 20 which is basically this line:

const parsedToken: ParsedToken | undefined = keycloak?.tokenParsed

,the windows.replaceState() function setting the URL params in the Growth component, and the specific reportq() function which is also in the Growth.

Here is my _app.tsx, I believe the parseCookies function is of interest here:

import React, { useEffect } from "react"
import App from 'next/app'
import { SSRKeycloakProvider, SSRCookies } from '@react-keycloak/ssr'
import cookie from 'cookie'
import type { IncomingMessage } from 'http'
import { ThemeProvider } from '@material-ui/core/styles';
import theme from '../theme';

const KC_URL = process.env.NEXT_PUBLIC_KC_URL;
const KC_REALM = process.env.NEXT_PUBLIC_KC_REALM
const KC_CLIENT_ID = process.env.NEXT_PUBLIC_KC_CLIENT_ID

const keycloakCfg = {
  realm: KC_REALM,
  url: KC_URL,
  clientId: KC_CLIENT_ID
}
interface InitialProps {
  cookies: unknown
}

export default function CustomApp({ Component, pageProps, cookies }) {
  
const initOptions = {
    onLoad: 'login-required',
    checkLoginIframe: false
  }

  return (
    <SSRKeycloakProvider
      keycloakConfig={keycloakCfg}
      persistor={SSRCookies(cookies)}
      initOptions={initOptions}
    >
      <ThemeProvider theme={theme}>
          <Component {...pageProps} />
      </ThemeProvider>
    </SSRKeycloakProvider>
  );
}

// I think the cookies and this function have to do with the issue
function parseCookies(req?: IncomingMessage) {
  if (!req || !req.headers) {
    return {}
}
  return cookie.parse(req.headers.cookie || '')
}

CustomApp.getInitialProps = async (appContext) => {
  // Your static props paths
  const staticPropsPaths = [
    "/paper/[paperId]/[paperName]",
    "/hubs"
  ]

  if (process.browser || !staticPropsPaths.includes(appContext.router.route)) {
    const appProps = await App.getInitialProps(appContext)
    return { ...appProps, cookies: parseCookies(appContext?.ctx?.req) }
  }
}

And here is my index.tsx:

import type { NextPage } from 'next'
import { useKeycloak } from '@react-keycloak/ssr'
import type { KeycloakInstance, KeycloakTokenParsed } from 'keycloak-js'
import Growth from '../containers/Admin/growth'

type ParsedToken = KeycloakTokenParsed & {
  email?: string
  preferred_username?: string
  given_name?: string
  family_name?: string
}

const Home = ({ query }) => {
   const { keycloak } = useKeycloak<KeycloakInstance>()
   const parsedToken: ParsedToken | undefined = keycloak?.tokenParsed

   const loggedinState = keycloak?.authenticated ? (
     <span className="text-success">logged in</span>
   ) : (
     <span className="text-danger">NOT logged in</span>
   )

  const welcomeMessage =
    keycloak?.authenticated || (keycloak && parsedToken)
      ? `Welcome back ${parsedToken?.preferred_username ?? ''}!`
      : 'Welcome ! Please login to continue.'

  return <Growth query={query} page={'home'} />
}

Home.getInitialProps = async ({ query, res }) => {
  return { query }
}

export default Home

the Growth code is below: and I believe the line of interest in the Growth component:

 window.history.replaceState('state', 'Growth ', `${BASE_URL}${page}${reportq}`)

The Growth Component:

const Growth = ({ query, page }) => {
    const { keycloak, initialized } = useKeycloak()
    const router = useRouter()
    let [p, setP] = useState(page)

    let reportq;
    let prospectq;
    
    // I believe this function is involved
    const setReportq = (params) => {
        window.sessionStorage.setItem("reportq", params)
    }

    const setProspectq = (params) => {
        window.sessionStorage.setItem("prospectq", params)
    }

    useEffect(() => {
        if (typeof window !== 'undefined') {
            reportq = window.sessionStorage.getItem('reportq'); 
        if (!reportq)
            reportq = '';
        prospectq = window.sessionStorage.getItem('prospectq');
        if (!prospectq)
            prospectq = '';
    }
    if (typeof window !== 'undefined' && page == 'report') {
        const keys = Object.keys(query);

        let params = "?";
        for (let i = 0; i < keys.length; i++) {
            params += `${keys[i]}=${encodeURIComponent(query[keys[i]])}&`
        }
        // console.log("eval reportq vs query", { reportq, params })
        if (!reportq && keys && keys.length > 0) {
            console.log("growth line 78 setting reportq")
            setReportq(params)
        }
        else {
            if (params != reportq) {
               console.log("growth line 81 updating url with ", reportq)
               window.history.replaceState('state', 'Growth ', `${BASE_URL}${page}${reportq}`)
            }
        }
    }
        })

    const key = query.key;

    const setPage = (page) => {
        setP(page)
        setTimeout(() => {
            const newUrl = `${BASE_URL}${page == 'home' ? '' : page}${page == 'report' ? reportq : page == 'prospect' ? prospectq ? prospectq : `?key=${key}` : `?key=${key}`}`
        router.push(newUrl);
        }, 1);
    }

return (
    <ThemeProvider theme={theme}>
        <>
            <Head>
                <title>Growth</title>
                <meta name="robots" content="noindex" />
                <meta name="theme-color" content="#2563FF" />
            </Head>
       
            <Layout>
                <Sider
                    style={{
                        overflow: 'auto',
                        height: '100vh',
                        position: 'fixed',
                        left: 0,
                    }}
                >
                    
                    <Menu theme="dark" mode="inline" defaultSelectedKeys={[page]}>
                        <Menu.Item key="report" onClick={() => setPage('report')}>
                            Reports
                        </Menu.Item>
                    </Menu>
                </Sider>
                <Layout className="site-layout" style={{ marginLeft: 200 }}>
                            {p == 'report' && <Report query={query} setReportq={setReportq} />}
                
                </Layout>
            </Layout>
        </>
    </ThemeProvider >);
}
Growth.getInitialProps = async ({ query, res }) => {
    return { query }
}
export default Growth;
codigomonstruo
  • 1,081
  • 1
  • 11
  • 45

1 Answers1

0

Change onLoad: 'login-required' to onLoad: 'check-sso',

devFl
  • 31
  • 2