为什么我在使用react-router时必须手动刷新页面才能让组件加载?

Why do I have to manually refresh the page for the component to load when using react-router?

我有一个 React 应用程序,它使用 react-router-dom 从侧边栏加载不同的组件。每当我点击边栏中的 link 时,URL 就会发生变化,但我必须手动刷新页面才能加载页面的实际内容。

我希望我的应用程序在单击边栏 link 时自动刷新页面,而不是用户必须手动刷新页面才能加载组件。

function App() {
  const [locale, setLocale] = useState('en');

  return (
    <>
      <Routes>
        <Route path="/" element={<IntlProvider locale={locale}><Layout setLocale={setLocale} /></IntlProvider>}>
          <Route index element={<Home />} />
          <Route path="experience" element={<Experience />} />
          <Route path="skills" element={<Skills />} />
          <Route path="portfolio" element={<Portfolio />} />
          <Route path="contact" element={<Contact />} />
        </Route>
      </Routes>
    </>

  );
}

export default App;

这是我的App.js

const Sidebar = ({ toggled, handleToggleSidebar }) => {
  const intl = useIntl();
  return (
    <ProSidebar
      toggled={toggled}
      breakPoint="md"
      onToggle={handleToggleSidebar}
    >
      <SidebarHeader>
        <div
          style={{
            padding: '24px',
            textTransform: 'uppercase',
            fontWeight: 'bold',
            fontSize: 14,
            letterSpacing: '1px',
            overflow: 'hidden',
            textOverflow: 'ellipsis',
            whiteSpace: 'nowrap',
          }}
        >
          <Link style={{ display: 'block', padding: '15px 0' }} to="/">
            <img style={{ display: 'block', margin: '-3em 3em', width: '133px', height: 'auto' }} src={LogoS} alt="Logo" />
          </Link>
        </div>
      </SidebarHeader>

      <SidebarContent>
        <Menu iconShape="circle">

          <MenuItem icon={<FaHome size={32} />}>
            {intl.formatMessage({ id: 'Home' })}
            <NavLink activeclassname="active" to="/"></NavLink>
          </MenuItem>

          <MenuItem icon={<FaProjectDiagram size={32} />}>
            {intl.formatMessage({ id: 'Experience' })}
            <NavLink to="/experience"></NavLink>
          </MenuItem>

          <MenuItem icon={<FaToolbox size={32} />}>
            {intl.formatMessage({ id: 'Skills' })}
            <NavLink to="/skills"></NavLink>
          </MenuItem>

          <MenuItem icon={<FaFolderOpen size={32} />}>
            {intl.formatMessage({ id: 'Portfolio' })}
            <NavLink to="/portfolio"></NavLink>
          </MenuItem>

          <MenuItem icon={<FaTelegramPlane size={32} />}>
            {intl.formatMessage({ id: 'Contact' })}
            <NavLink to="/contact"></NavLink>
          </MenuItem>

        </Menu>
      </SidebarContent>
    </ProSidebar >
  );
};

export default Sidebar;

这是我的Sidebar.js

function Layout({ setLocale }) {
  const [rtl, setRtl] = useState(false);
  const [collapsed, setCollapsed] = useState(false);
  const [image, setImage] = useState(true);
  const [toggled, setToggled] = useState(false);

  const handleCollapsedChange = (checked) => {
    setCollapsed(checked);
  };

  const handleRtlChange = (checked) => {
    setRtl(checked);
    setLocale(checked ? 'ar' : 'en');
  };
  const handleImageChange = (checked) => {
    setImage(checked);
  };

  const handleToggleSidebar = (value) => {
    setToggled(value);
  };

  return (
    <div className={`app ${rtl ? 'rtl' : ''} ${toggled ? 'toggled' : ''}`}>
      <Sidebar
        image={image}
        collapsed={collapsed}
        rtl={rtl}
        toggled={toggled}
        handleToggleSidebar={handleToggleSidebar}
      />
      <Main
        image={image}
        toggled={toggled}
        collapsed={collapsed}
        rtl={rtl}
        handleToggleSidebar={handleToggleSidebar}
        handleCollapsedChange={handleCollapsedChange}
        handleRtlChange={handleRtlChange}
        handleImageChange={handleImageChange}
      />
      <Outlet />
    </div>
  );
}

export default Layout;

这是呈现页面的 Layout.js。

const Contact = () => {
  const [letterClass, setLetterClass] = useState('text-animate')
  const form = useRef()

  useEffect(() => {
    return setTimeout(() => {
      setLetterClass('text-animate-hover')
    }, 3000)
  }, [])

  const sendEmail = (e) => {
    e.preventDefault()

    emailjs
      .sendForm(
        'service_v8uv1al',
        'template_xge6tgj',
        form.current,
        'UuX3z3S-mWAnAL7BY')
      .then(
        () => {
          alert('Message successfully sent!')
          window.location.reload(false)
        },
        () => {
          alert('Failed to send the message, please try again')
        }
      )
  }

  return (
    <>
      <div className="container contact-page">
        <div className="text-zone">
          <h1>
            <AnimatedLetters
              letterClass={letterClass}
              strArray={['C', 'o', 'n', 't', 'a', 'c', 't', ' ', 'm', 'e']}
              idx={15}
            />
          </h1>
          <div className='app_footer-cards'>
            <div className='app_footer-card'>
              <img src={emailLogo} alt="email" />
              <a href="mailto:k.maumau11@gmail.com" className='p-text'>k.maumau0@gmail.com</a>
            </div>
            <div className='app_footer-card'>
              <img src={phoneLogo} alt="mobile" />
              <a href="tel: +1 (832) 764-9796" className='p-text'>+1 (832) 764-9796</a>
            </div>
          </div>
          <p>
            If you have a request or question, or simply just want to
            say Hello don't hesitate to contact me using the form below!
          </p>
          <div className="contact-form">
            <form ref={form} onSubmit={sendEmail}>
              <ul>
                <li className="half">
                  <input placeholder="Name" type="text" name="name" required />
                </li>
                <li className="half">
                  <input
                    placeholder="Email"
                    type="email"
                    name="email"
                    required
                  />
                </li>
                <li>
                  <input
                    placeholder="Subject"
                    type="text"
                    name="subject"
                    required
                  />
                </li>
                <li>
                  <textarea
                    placeholder="Message"
                    name="message"
                    required
                  ></textarea>
                </li>
                <li>
                  <input type="submit" className="flat-button" value="SEND" />
                </li>
              </ul>
            </form>
          </div>
        </div>
      </div>
      <Loader type="ball-scale" />
    </>
  )
}

export default Contact

联系页面代码

问题

您的应用程序代码在离开页面时实际上崩溃了。目前尚不清楚为什么您没有看到 React 错误页面(也许您是 运行 生产构建 ),但问题是对于每个页面您都使用 useEffect 挂钩设置超时以更新某些本地状态。

useEffect(() => {
  return setTimeout(() => {
    setLetterClass('text-animate-hover');
  }, 3000);
}, []);

这个问题是 React 假设 anything returned from the useEffect hook 是一个清理函数,当组件被调用时rerendering/unmounting。 setTimeout returns 代表计时器 ​​ID 的数字。

重新加载页面实际上是在当前 URL 路径上重新加载整个应用程序。

解决方案

在每个页面上,将逻辑重构为 return 清除超时的清理函数。

useEffect(() => {
  const timer = setTimeout(() => {
    setLetterClass('text-animate-hover');
  }, 3000);
  return () => clearTimeout(timer);
}, []);

这允许在组件安装时实例化超时,并且对于用户离开页面并且卸载该组件的偶然机会,它将清除超时并且不会调用将排队的回调状态状态。

建议

AnimatedLetters 组件的每个页面似乎都具有相同的动画 letterClass 状态。与其在所有页面上复制相同的 code/logic,不如将其抽象为自定义 React 挂钩,该挂钩 return 是 letterClass 值。

示例:

const useLetterClass = ({
  start = 'text-animate',
  end = 'text-animate-hover'
}) => {
  const [letterClass, setLetterClass] = useState(start);

  useEffect(() => {
    const timer = setTimeout(() => {
      setLetterClass(end);
    }, 3000);
    return () => clearTimeout(timer);
  }, []);

  return { letterClass };
};

在每个页面组件中导入 useLetterClass 挂钩、调用并传递 returned letterClass 属性值。

示例:

const Contact = () => {
  const { letterClass } = useLetterClass({
    start: 'text-animate',
    end: 'text-animate-hover',
  });
  const form = useRef();

  const sendEmail = (e) => {
    ...
  }

  return (
    <>
      <div className="container contact-page">
        <div className="text-zone">
          <h1>
            <AnimatedLetters
              letterClass={letterClass}
              strArray={['C', 'o', 'n', 't', 'a', 'c', 't', ' ', 'm', 'e']}
              idx={15}
            />
          </h1>
          ...
        </div>
      </div>
      <Loader type="ball-scale" />
    </>
  );
};

您提供的沙箱代码似乎也存在与 SCSS 的 some 相关的问题,特别是缺少 $sidebar-color 变量。我对 SCSS 不太熟悉,但我不怀疑它与我上面描述的 routing/navigation 问题有关。