Изменение состояния реакции при переходе к новому пути

#reactjs #react-hooks #gatsby #use-effect #use-state

#reactjs #реагирующие крючки #gatsby #использование-эффект #use-state

Вопрос:

Я пытаюсь реализовать react-transition-group , и мне нужно иметь возможность изменять состояние fadeEffectVisible от false до true , предполагая, что путь, по которому переходит пользователь, не совпадает с текущим путем. Другими словами, это должно работать, если пользователь переходит от page-1 к page-2 , но не если пользователь включен page-1 и нажимает на ссылку page-1 . Я использую хуки и функциональные компоненты, и это мой AppLayout компонент в его нынешнем виде.

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

Я даже близок к тому, чтобы быть на правильном пути здесь? Спасибо!

Комментарии:

1. path !== path всегда будет false . Вам нужно сравнить текущее значение пути с предыдущим значением пути? Как получить предыдущие реквизиты или состояние? Или скорее вам нужно протестировать целевой путь по текущему пути?

2. Мне нужно проверить целевой путь на соответствие текущему пути.

3.Итак, проверка чего-то вроде targetPath !== path перед навигацией? Можете ли вы просто полностью запретить навигацию, если она уже находится на этом пути? Я думаю, нам нужно немного больше контекста кода. Можете ли вы предоставить более полный пример кода компонента?

4. Вопрос обновлен дополнительным кодом. Спасибо, что взглянули.

5. Я думаю, вы можете быть близки, но неясно, где path определено, откуда оно взялось. При условии, что оба path параметра и prevPath и зависимость эффекта верны, более лаконичной реализацией будет setfadeEffectVisible(prevPath !== path) int he effect . Это приведет к задержке fadeEffectVisible значения на цикл рендеринга, и я не уверен, что это именно то, что вам нужно. Значение состояния, по-видимому, просто является производным значением от обоих prevPath и path , поэтому, возможно, просто вычислите это в текущем цикле рендеринга, т.Е. in={prevPath !== path} ?

Ответ №1:

Я бы использовал другой подход, используя useRef hook для сохранения предыдущего пути страницы (сохраненного в a useState ) и сравнения между ними.

Создание пользовательского хука типа:

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

  return ref.current;
}
  

И добавьте следующие строки в свой компонент:

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

После этого вам нужно только сравнить location.pathname !== prevPage .

location prop является prop открытым Gatsby (потому что он распространяется @reach/router из React) только в компонентах (страницах) верхнего уровня. Возможно pathname , свойство не совсем соответствует вашим потребностям, проверьте его, чтобы узнать, какое свойство соответствует вашим требованиям.

Ссылки:

Комментарии:

1. Спасибо, это здорово. Однако я столкнулся с небольшой проблемой, которая заключается в том, что const prevPage = usePrevious(page) никогда не обновляется до самой ближайшей предыдущей страницы; он возвращает только первую state .

2. Возможно, вы можете еще немного поиграть с хуками (не нарушая правил хуков). Попробуйте создать a useEffect с местоположением в качестве зависимости, чтобы запустить ваш usePrevious .

Ответ №2:

Понял это, благодаря большой помощи от ответа Феррана, но я хотел поделиться своим полным компонентом теперь, когда он у меня работает. В частности, именно так я его настроил, чтобы правильно обновлять мое page состояние и проверять предыдущее url на соответствие целевому.

 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 amp;amp; <Devtools />}
                </div>
              </CSSTransition>
            </TransitionGroup>
          </AppContext>
        </>
      )}
    />
  );
};