Skip to content
Advertisement

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

enter image description hereI have a React application that uses react-router-dom to load different components from the sidebar. Whenever I click the link in the sidebar, the URL changes, but I must manually refresh the page to get the actual content of the page to load.

I want my application to automatically refresh the page when a sidebar link is clicked instead of the user having to manually refresh the page in order for a component to load.

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;

This is my 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;

This is my 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;

This is the Layout.js that renders the page.

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

Contact Page Code

Advertisement

Answer

Issue

Your app code is actually crashing when navigating away from a page. It’s not clear why you aren’t seeing the React error page (perhaps you are running a production build) but the issue is that for each page you are using a useEffect hook to set a timeout to update some local state.

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

The issue with this is that React assumes that anything returned from the useEffect hook is a cleanup function to be invoked when the component is rerendering/unmounting. setTimeout returns a number representing the timer id.

Reloading the page is effectively reloading the entire app on the current URL path.

Solution

On each of these pages, refactor the logic to return a cleanup function that clears the timeout.

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

This allows the timeout to be instantiated when the component mounts, and for the offhand chance the user navigates away from the page and this component unmounts, it will clear the timeout and won’t invoke the callback that will enqueue a state state.

Suggestion

It seems that each of these pages has the same animation letterClass state for the AnimatedLetters component. Instead of duplicating the same code/logic across all pages, abstract it into a custom React hook that returns the letterClass value.

Example:

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

In each page component import the useLetterClass hook, call, and pass the returned letterClass prop value.

Example:

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

Edit why-do-i-have-to-manually-refresh-the-page-for-the-component-to-load-when-using

The sandbox code you provided also seems to have an issue with some of the SCSS, specifically with a $sidebar-color variable missing. I’m not familiar with SCSS much, but I don’t suspect it is related to the routing/navigation issue I described above.

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement