#StackBounty: #javascript #reactjs #gatsby #use-effect #prismic.io Page Templates re-rendering on every click in Prismic & Gatsby

Bounty: 50

I’ve been trying to diagnose why I’ve got useEffect running multiple times when I click a <Link> component, even with empty dependencies. I found a good way to figure out where my problem lies, using the solution in this answer.

Essentially I’m adding a useEffect that console logs components went they re-render, and I went all the way up the tree till I found the highest level component that doesn’t re-render.

useEffect(()=>{
  console.log('this component is rendered');
},[]);

I’m using gatsby-plugin-layout, and my AppLayout component is the component where the re-render does not occur, meaning that the problem lies within my page templates, as defined in gatsby-source-prismic-graphql, which are the next things down the tree where the console logs the page change when the <Link> is clicked. All this to say, I’m not entirely sure where the problem here lies, or if there is something within my AppLayout or gatsby-config.js that is throwing it off and making this behavior occur. How can I fix it so my pages aren’t constantly re-rendering? Included below are my AppLayout and gatsby-config.js.

Thanks in advance.

gatsby-config.js

/* eslint-disable @typescript-eslint/camelcase */
const path = require('path');

require('dotenv').config({
  path: `.env.${process.env.NODE_ENV}`,
});

const site = require('./config/site');

const pathPrefix = site.pathPrefix === '/' ? '' : site.pathPrefix;

module.exports = {
  pathPrefix: site.pathPrefix,
  siteMetadata: {
    siteUrl: site.url + pathPrefix,
    pathPrefix,
    title: site.title,
    titleAlt: site.titleAlt,
    titleTemplate: site.titleTemplate,
    description: site.description,
    banner: site.logo,
    headline: site.headline,
    siteLanguage: site.siteLanguage,
    ogLanguage: site.ogLanguage,
    author: site.author,
    twitter: site.twitter,
    facebook: site.facebook,
  },
  plugins: [
    'gatsby-plugin-react-helmet-async',
    'gatsby-plugin-typescript',
    'gatsby-plugin-sharp',
    'gatsby-plugin-sitemap',
    'gatsby-transformer-sharp',
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        name: 'images',
        path: path.join(__dirname, `src`, `assets`, `images`),
      },
    },
    {
      resolve: 'gatsby-plugin-manifest',
      options: {
        name: 'one-day-doors-and-closets',
        short_name: 'starter',
        start_url: '/',
        background_color: '#663399',
        theme_color: '#663399',
        display: 'minimal-ui',
        icon: 'src/assets/images/icon.png',
      },
    },
    {
      resolve: '@sentry/gatsby',
      options: {
        dsn: process.env.SENTRY_DSN,
        enabled: (() => ['production'].indexOf(process.env.NODE_ENV) !== -1)(),
      },
    },
    {
      resolve: 'gatsby-plugin-react-svg',
      options: {
        rule: {
          include: path.resolve(__dirname, 'src/assets/svg'),
        },
      },
    },
    {
      resolve: 'gatsby-source-prismic-graphql',
      options: {
        repositoryName: `${process.env.PRISMIC_REPOSITORY_NAME}`,
        defaultLang: 'en-us',
        path: '/preview',
        previews: false,
        accessToken: `${process.env.PRISMIC_ACCESS_TOKEN}`,
        pages: [
          {
            type: 'Homepage',
            match: '/',
            component: require.resolve('./src/pages/index.tsx'),
          },
          {
            type: 'Landing',
            match: '/:uid',
            component: require.resolve('./src/templates/landing.tsx'),
          },
          {
            type: 'Legal',
            match: '/:uid',
            component: require.resolve('./src/templates/legal.tsx'),
          },
          {
            type: 'Locator',
            match: '/:uid',
            component: require.resolve('./src/pages/locations.tsx'),
          },
          {
            type: 'Product',
            match: '/products/:uid',
            component: require.resolve('./src/templates/product.tsx'),
          },
        ],
      },
    },
    {
      resolve: 'gatsby-plugin-layout',
      options: {
        component: require.resolve('./src/components/app-layout/AppLayout.tsx'),
      },
    },
  ],
};

AppLayout.tsx

import React, { ReactNode, useEffect, useState } from 'react';

import { Devtools } from 'components/devtools/Devtools';
import { Footer } from 'components/footer/Footer';
import { Header } from 'components/header/Header';
import { NavigationSkipLink } from 'components/navigation-skip-link/NavigationSkipLink';
import { AppContext } from 'contexts/app-context/AppContext';
import { graphql, StaticQuery } from 'gatsby';
import { usePrevious } from 'hooks/use-previous';
import { TransitionGroup, CSSTransition } from 'react-transition-group';

import s from './AppLayout.scss';

interface AppLayoutProps {
  props: any;
  children: ReactNode;
  location: any;
}

const isDev = process.env.NODE_ENV === 'development';

export const MainContentId = 'maincontent';
export const timeout = 250;

const NavQuery = graphql`
  query NavQuery {
    prismic {
      allNavigations {
        edges {
          node {
            ...NotificationBar
            ...NavigationItems
            ...FooterNavigationItems
            ...LegalNavigationItems
          }
        }
      }
    }
  }
`;

// eslint-disable-next-line react/display-name
export default ({ children, location: { pathname } }: AppLayoutProps) => {
  const [fadeEffectVisible, setFadeEffectVisible] = useState(false);
  const [page, setPage] = useState(pathname);
  const prevPage = usePrevious(pathname);

  useEffect(
    () => () => {
      if (pathname !== prevPage) {
        setFadeEffectVisible(true);
        setPage(page);
      }
    },
    [pathname],
  );

  const handleFadeEffectEntered = () => {
    setTimeout(() => {
      setFadeEffectVisible(false);
    }, 50);
  };

  return (
    <StaticQuery
      query={`${NavQuery}`}
      render={(data) => (
        <>
          <AppContext>
            <CSSTransition
              in={fadeEffectVisible}
              timeout={timeout}
              classNames={{
                enter: s.fadeEffectEnter,
                enterActive: s.fadeEffectEnterActive,
                enterDone: s.fadeEffectEnterDone,
                exit: s.fadeEffectExit,
                exitActive: s.fadeEffectExitActive,
              }}
              onEntered={handleFadeEffectEntered}
            >
              <div className={s.fadeEffect} aria-hidden="true" />
            </CSSTransition>

            <NavigationSkipLink />
            <Header navigationContent={data.prismic.allNavigations.edges[0].node} />
            <TransitionGroup component={null}>
              <CSSTransition
                key={pathname}
                timeout={timeout}
                classNames={{
                  enter: s.pageEnter,
                }}
              >
                <div id={MainContentId} className={s.layout}>
                  {children}

                  <Footer navigationItems={data.prismic.allNavigations.edges[0].node} />

                  {isDev && <Devtools />}
                </div>
              </CSSTransition>
            </TransitionGroup>
          </AppContext>
        </>
      )}
    />
  );
};


Get this bounty!!!

#StackBounty: #javascript #reactjs #use-effect #react-functional-component React Maximum update depth exceeded in functional component

Bounty: 100

In my react functional component, I have a function that is executed to toggle the checkboxes as selected or unselected. It works fine.

The function goes like this:

const handleCheckboxChange = useCallback((value) => {
    const selectedFilters = state.identifiers_checkbox[
        "selected_checkboxes"
    ].find((obj) => obj === value);

    const selected_checkboxes = selectedFilters
        ? state.identifiers_checkbox["selected_checkboxes"].filter(
            (obj) => obj !== value
        )
        : [...state.identifiers_checkbox["selected_checkboxes"], value];

    const toggled_checkboxes = {
        selected_checkboxes,
        options_checkbox: [
            ...state.identifiers_checkbox.options_checkbox.map((filter) => {
                return {
                    ...filter,
                    options: filter.options.map((ele) => {
                        return {
                            ...ele,
                            checked: selected_checkboxes.includes(ele.key),
                        };
                    }),
                };
            }),
        ],
    };

    dispatch({
        type: "update-checkboxes",
        payload: {
            selected_checkboxes: toggled_checkboxes.selected_checkboxes,
            options_checkbox: toggled_checkboxes.options_checkbox,
        },
    });
}, [dispatch, state.identifiers_checkbox]);

Additionaly, I need to call this function as the component mounts & then automatically mark any of the checkbox as selected according to the name of the checkbox passed as argument.

For this, my approach is:

useEffect(() => {
        //need to invoke handleCheckboxChange if this true
        if (info.prevui === 'intervention') {
            // if all the checkboxes has been fetched with initial state as false/unselected
            if (state.identifiers_checkbox.options_checkbox.length > 1) {
                handleCheckboxChange("label1");
            }
        }
    }, [state.identifiers_checkbox, info.prevui, handleCheckboxChange]);

The "label1" is the one of the value/key of a checkbox that needs to be set to checked:true. The handler function works fine if it is invoked onChange() of any of the checkbox, but when trying to invoke it on render on the basis of a particular condition, it doesn’t works.

It throws Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

Since, I need to execute this function only once even on render also i.e. (when info.ui === intervention’ & info.ui also does not change every time ), this error behaviour of useEffect() is out of my understanding.

Can anyone please helpout to understand and provide any working hint/solution for the same?

Thanks


Get this bounty!!!