0

Here's what I'm dealing with:

  • Single page app built with react + react-router-dom
  • User clicks on a <Link to={"/new-page-route"}/>
  • URL changes and my <Route/> starts rendering a new <Component/>

I mean, React is fast, but my new component takes a while to render because it's a whole new page. So it should take something between 200 and 400ms. And if I get no UI feedback, it feels that my click has not worked.

I need some kind of UI feedback so my user's know their click has been fired and something is going on. I don't think I need loaders or anything, but something to show that the click has been "accepted" by the UI.

In theory that could be handled by CSS using:

  • -webkit-tap-highlight-color: #SOMECOLOR
  • active: #SOMECOLOR

But somehow, when the URL changes and the new render begins, the browser is not being able to paint those CSS results to the screen, at least this is the behavior on Chrome and Firefox. It gets kind of weird, sometimes I see the tap-highlight and the active-change but almost always I don't see it.

NOTE: This is not the 300ms default delay on mobile that waits for the double tap. I've dealt with that using the appropriate tags.

What I thought about doing is:

  • Stop using the <Link/> component and use a normal <a/>.
  • Create a clicked state to be update after the click event
  • Call event.preventDefault() to prevent normal behavior of the <a/> navigation
  • Use the clicked state to render some new styles for the UI feedback
  • Fire history.push("/new-page-route") on a useEffect after clicked state has become true

Something like:

const newUrl = "/new-page-route";
const [clicked,setClicked] = useState(false);

const history = useHistory();      // HOOK FROM react-router-dom

function handleLinkClick(event) {
  event.preventDefault();
  setClicked(true);
}

useEffect(() => {

  if (clicked === true) {

    history.push(newUrl);

    // OR MAYBE ADD A TIMEOUT TO BE EXTRA SURE THAT THE FEEDBACK WILL BE PAINTED
    // BECAUSE I'VE SEEN THE BROWSER NOT BEING ABLE TO PAINT IF I DON'T GIVE IT SOME EXTRA TIME
    setTimeout(() => history.push(newUrl),100);

  }

},[clicked,history]);


// USE THE clicked STATE TO RENDER THE UI FEEDBACK (CHANGE TEXT COLOR, WHATEVER I NEED);

QUESTION

Has anyone had this issue before? What is a good way of solving this? I guess that in theory the browser should be able to paint before the new render begins, but this is not what I'm getting.

SANDBOX WITH THE ISSUE

https://codesandbox.io/s/nice-monad-4fwoc <=== CLICK HERE TO SEE THE CODE

https://4fwoc.csb.app <=== CLICK HERE TO SEE THE RESULT ONLY

  • On desktop: I'm able to see the BLUE background for the active state when clicking the link
  • On mobile: I don't see any CSS change when clicking the links. Unless I tap AND HOLD
  • Comments: Imagine that your component takes a while to render. Without any UI feedback, it feels that you haven't clicked at all, even though it is rendering on background.
import React from "react";
import { Link, Switch, Route } from "react-router-dom";
import styled from "styled-components";
import "./styles.css";

const HOME = "/";
const ROUTE1 = "/route1";
const ROUTE2 = "/route2";

const LS = {};

// REGULAR CSS RULES FOR THE className="link" ARE ON "./styles.css"

LS.Link_LINK = styled(Link)`
  color: black;
  -webkit-tap-highlight-color: red;
  &:active {
    background-color: blue;
  }
`;

export default function App() {
  return (
    <React.Fragment>
      <div>
        <Switch>
          <Route exact path={HOME} component={Home} />
          <Route exact path={ROUTE1} component={Component1} />
          <Route exact path={ROUTE2} component={Component2} />
        </Switch>
      </div>
    </React.Fragment>
  );
}

function Home() {
  return (
    <React.Fragment>
      <div>I am Home</div>
      <LS.Link_LINK to={ROUTE1}>Route 1 (using styled-components)</LS.Link_LINK>
      <br />
      <Link className={"link"} to={ROUTE1}>
        Route 1 (using regular CSS)
      </Link>
    </React.Fragment>
  );
}

function Component1() {
  return (
    <React.Fragment>
      <div>I am Component1</div>
      <LS.Link_LINK to={ROUTE2}>Route 2 (using styled-components)</LS.Link_LINK>
      <br />
      <Link className={"link"} to={ROUTE2}>
        Route 2 (using regular CSS)
      </Link>
    </React.Fragment>
  );
}

function Component2() {
  return (
    <React.Fragment>
      <div>I am Component2</div>
      <LS.Link_LINK to={HOME}>Home (using styled-components)</LS.Link_LINK>
      <br />
      <Link className={"link"} to={HOME}>
        Home (using regular CSS)
      </Link>
    </React.Fragment>
  );
}

cbdeveloper
  • 27,898
  • 37
  • 155
  • 336

1 Answers1

0

You should use two loading phases.

  1. Use lazy loading when loading the "heavy" page and display a skeleton component as a fallback to show the user that something is loading.
  2. Use a component/redux state loading indicator for when your page is loading data from apis

Trying to achive that with react-router links is not the best way because routing happends instantly, it is not the thing that stalls your application. Api calls and multiple re-renders cause an application to behave slowly.

Tasos
  • 1,880
  • 13
  • 19
  • Thanks, Tasos. There are no APIs being called during this route change. But it renders multiple components, that render multiple dropdowns, and svg chart, and some other stuff, so it takes a while. The data for all that is already present when the route is being changed. It's not a huge delay. It's still much faster than fetching the page from the server and I'm ok with letting the user wait 200-400ms. I just need to be clear that his click was "accepted" and is being "processed", or something like that. That is why I've though of a `tap-highlight` or something. But it does not work. – cbdeveloper May 13 '20 at 12:17
  • This is only a problem on Mobile devices, that have less CPU power. On desktop the page change is basically real-time, so I don't need a click feedback. The feedback is the page change itself. I think I would be adding extra complexity unnecessarily if I were to implement lazy-loading and multiple render cascades just for that. Specially with no API calls involved. What do you think? – cbdeveloper May 13 '20 at 12:58
  • Since the url changes and a new component is being loaded you cannot do much, what would work for you is https://www.npmjs.com/package/react-redux-loading-bar and I also found this that might help you: https://stackoverflow.com/a/40989121/5605822 – Tasos May 13 '20 at 14:26