Change React State if Navigating to New Path

Viewed 602

I am trying to implement react-transition-group, and need to be able to change a state of fadeEffectVisible from false to true assuming that the path that a user is navigating to is not the same as the current path. In other words, it should work if a user navigates from page-1 to page-2 but not if a user is on page-1 and clicks a link to page-1. I'm using hooks and functional components, and this is my AppLayout component as it stands right now.

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

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 { TransitionGroup, CSSTransition } from 'react-transition-group';

import s from './AppLayout.scss';

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

export const MainContentId = 'maincontent';

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

// eslint-disable-next-line react/display-name
export default ({ children, location }: AppLayoutProps) => {
  const prevPath = location.href;

  const [fadeEffectVisible, setfadeEffectVisible] = useState(true);

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

  useEffect(() => {
    if (prevPath !== path) {
      setfadeEffectVisible(true);
    } else {
      setfadeEffectVisible(false);
    }
  }, [path]);

  return (
    <StaticQuery
      query={`${NavQuery}`}
      render={(data) => (
        <>
          <AppContext>
            <NavigationSkipLink />
            <Header navigationContent={data.prismic.allNavigations.edges[0].node} />

            <CSSTransition
              in={fadeEffectVisible}
              timeout={150}
              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>

            <TransitionGroup component={null}>
              <CSSTransition
                key={path}
                timeout={150}
                classNames={{
                  enter: s.pageEnter,
                }}
              >
                <div id={MainContentId} className={s.layout}>
                  {children}

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

                </div>
              </CSSTransition>
            </TransitionGroup>
          </AppContext>
        </>
      )}
    />
  );
};

Am I even close to being on the right path here? Thanks!

2 Answers

I would use another approach using useRef hook to store the previous page path (stored in a useState) and make a comparison between them.

Creating a custom hook like:

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });

  return ref.current;
}

And add the following lines in your component:

  const [page, setPage] = useState(location.pathname);
  const prevPage = usePrevious(page) // <-- look here for your previous page

Afterward, you only need to compare location.pathname !== prevPage.

location prop is a prop exposed by Gatsby (because it extends from @reach/router from React) only in the top-level components (pages). Maybe pathname property doesn't fit exactly your needs, check it out to find out which property fits your requirements.

References:

Figured this out, thanks to a great deal of help from Ferran's answer, but I wanted to share my full component now that I've got it working. Specifically, this is how I got it set up to correctly update my page state, and check the previous url against the target.

const [page, setPage] = useState(pathname);

const prevPage = usePrevious(pathname);

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

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

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

  const timeout = 250;

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

  console.log(prevPage);

  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>
        </>
      )}
    />
  );
};
Related