Flashing screen when changing route with Reactjs

Viewed 1453

I have a simple route system, where I have a page as a Container (Home Page) and inside it I have two sections (NewPage and StoredPage).

I'm leaving all the route settings in one place, which is in App.tsx. I do this to stay centralized and have better control (I don't know if I'm doing it wrong).

The problem that happens is that, when changing the section, it is blinking, for some reason it is reloading the page again.

NOTE: I use react-router-guards

APP.TSX

import HomePage from 'pages/HomePage';
import StoredPage from 'pages/StoredPage';
import { memo, useMemo } from 'react';
import { BrowserRouter, Switch, Redirect } from 'react-router-dom';
import { GuardedRoute, GuardProvider, Next } from 'react-router-guards';
import { GuardFunctionRouteProps, GuardToRoute } from 'react-router-guards/dist/types';

export const Main = memo(() => {
    const RequireLoginGuard = (to: GuardToRoute, from: GuardFunctionRouteProps | null, next: Next) => next();

    return (
        <ErrorBoundary instance={instance}>
            <BrowserRouter>
                <GuardProvider guards={[RequireLoginGuard]} loading={() => <div>Loading Global</div>}>
                    <Switch>
                        <GuardedRoute exact path='/new' loading={() => <div>Loading New Page</div>} component={HomePage} />
                        <GuardedRoute exact path='/stored' loading={() => <div>Loading Stored Page</div>} component={HomePage} />
                        <GuardedRoute path='*' component={() => <Redirect to='/new' />} />
                    </Switch>
                </GuardProvider>
            </BrowserRouter>
        </ErrorBoundary>
    );
});

HOMEPAGE.TSX

import NewPage from 'pages/NewPage';
import StoredPage from 'pages/StoredPage';
import { useMemo } from 'react';
import { Route, Router, Switch } from 'react-router-dom';
import SectionTitle from 'xpi-black-ui/Common/SectionTitle';
import Navigation from '../../components/Navigation';
import MainContainer, { SectionContainer } from './styles';

const HomePage = () => {
    const navigationItems = useMemo(
        () => [
            {
                key: 'new',
                title: 'New Quotation',
                link: '/new',
                component: () => <NewPage />,
            },
            {
                key: 'stored',
                title: 'New Quotation 2',
                link: '/stored',
                component: () => <StoredPage />,
            },
        ],
        []
    );

    return (
        <MainContainer>
            <SectionContainer>Aaaaaaaaaaaaaaa</SectionContainer>
            <SectionContainer isSecondary>
                <SectionTitle>
                    <Navigation />

                    <div>C</div>
                </SectionTitle>

                <Switch>
                    {navigationItems.map(({ key, link, component: PageComponent }) => (
                        <Route key={key} path={link} component={PageComponent} exact />
                    ))}
                </Switch>
            </SectionContainer>
        </MainContainer>
    );
};

NAVIGATION.TSX

import { useCallback } from 'react';
import { useLocation } from 'react-router-dom';
import NavLink, { Container } from './styles';

const Navigation = () => {
    const { pathname } = useLocation();
    const isActive = useCallback((linkPathSelected) => pathname === linkPathSelected, [pathname]);
    const links = [
        { key: 'new', to: '/new', title: 'Criar Cotação' },
        { key: 'stored', to: '/stored', title: 'Cotações Armazenadas' },
    ];

    return (
        <Container>
            {links.map(({ key, to, title }) => (
                <NavLink exact key={key} to={to} $isActive={isActive(to)}>
                    {title}
                </NavLink>
            ))}
        </Container>
    );
};
2 Answers

From what I can tell, the issue is in your Homepage component with the way you define your navigationItems array's component properties combined with how the Route component treats the component prop when a function is passed to it.

const navigationItems = useMemo(
  () => [
    {
      key: 'new',
      title: 'New Quotation',
      link: '/new',
      component: () => <NewPage />,    // <-- function
    },
    {
      key: 'stored',
      title: 'New Quotation 2',
      link: '/stored',
      component: () => <StoredPage />, // <-- function
    },
  ],
  []
);

...

<Switch>
  {navigationItems.map(({ key, link, component: PageComponent }) => (
    <Route
      key={key}
      path={link}
      component={PageComponent} // <-- function passed through
      exact
    />
  ))}
</Switch>

Route component:

When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop (below).

The solution is to define the navigationItems object properties in terms of the Route component's props API. If you are passing components through that you want to me rendered on the component prop then they should be references to the React components instead of functions returning JSX.

const navigationItems = useMemo(
  () => [
    {
      key: 'new',
      title: 'New Quotation',
      link: '/new',
      component: NewPage,
    },
    {
      key: 'stored',
      title: 'New Quotation 2',
      link: '/stored',
      component: StoredPage,
    },
  ],
  []
);

...

<Switch>
  {navigationItems.map(({ key, link, ...props }) => (
    <Route key={key} exact path={link} {...props} />
  ))}
</Switch>

You may want to do the same thing for the link property, renaming it to path so it aligns more with its usage.

const navigationItems = useMemo(
  () => [
    {
      key: 'new',
      title: 'New Quotation',
      path: '/new',
      component: NewPage,
    },
    {
      key: 'stored',
      title: 'New Quotation 2',
      path: '/stored',
      component: StoredPage,
    },
  ],
  []
);

...

<Switch>
  {navigationItems.map(({ key, ...props }) => (
    <Route key={key} exact {...props} />
  ))}
</Switch>

Drew answer helped me, but to really solve the problem, I needed to insert something in the Guard loading, in this case I inserted the HomePage itself:

<GuardProvider guards={[RequireLoginGuard]} loading={HomePage}>
    <Switch>
      <GuardedRoute exact path='/new' component={HomePage} />
      <GuardedRoute exact path='/stored' component={HomePage} />
      <GuardedRoute path='*' component={() => <Redirect to='/new' />} />
   </Switch>
</GuardProvider>
Related