Emmanuel Odioko I am a frontend technology enthusiast, intrigued by frameworks and how they work, presently specializing in React, and would love to teach on any topic under it.

Implementing pull-to-refresh in React with Tailwind CSS

6 min read 1877

Implementing Pull-to-refresh in React with Tailwind CSS

Many of us have probably gotten sucked into particularly captivating videos and posts on social media and don’t want to stop scrolling. When the media algorithm starts recommending less fascinating posts, your best option is likely to swipe to pull down the screen, which initiates a gesture that retrieves new suggested data or posts. If this doesn’t capture your attention, you may repeat the process in some cases until the algorithm meets your satisfaction.

The gesture that initiates the retrieval of your next entertaining post is pull-to-refresh, which we will be discussing throughout the article.

Jump ahead:

What is pull-to-refresh?

When the user drags from the top of the page, a loader should appear and should only be visible a bit below the top screen at this point. This entire action indicates to the user that new data is being retrieved.

When the user lets go of the drag triggered by their touch, the loader disappears and at the same time, new data should appear in the feed. At the end of the article, we should have something resembling the GIF below:

The Demo App Showcasing a Pull-to-refresh Feature

This action is carried out by dragging the page’s top, and if there is a fresh update, it displays just immediately. Swiping or pulling makes it easy to retrieve info; this action was created by Loren Brichter in the Tweetie app in 2008, and it quickly became popular like anything excellent would.

Benefits of pull-to-refresh

Numerous smartphone apps use the pull-to-refresh gesture, which isn’t relatively new. They do so for the following reasons:

  • This action reduces screen clutter and frees up space
  • It provides room to view fresh data from the server

What is overscroll behavior?

The overscroll CSS attribute determines what a browser does when it approaches either the bottom or the top edge of a scrolling zone. By default, the mobile browser tends to refresh a page when the top of the page is reached. In most cases, as a developer intending to implement a custom pull-to-refresh, this default behavior is not desirable, so you can employ the use of overscroll behaviors to override the browser’s default pull-to-refresh gesture.

The code below is a general implementation of overscroll behavior from the MDN docs:

/* Keyword values */
overscroll-behavior: auto; /* default */
overscroll-behavior: contain;
overscroll-behavior: none;

/* Two values */
overscroll-behavior: auto contain;

/* Global values */
overscroll-behavior: inherit;
overscroll-behavior: initial;
overscroll-behavior: revert;
overscroll-behavior: revert-layer;
overscroll-behavior: unset;

To override the browser’s inbuilt pull-to-refresh gesture in our quest to build our own, we’ll use the overscroll-behavior: contain; property. However, because we are aiming for the vertical edge of our scrolling region, the overscroll-behavior in our case will be overscroll-behavior-y: contain.

To use this, we will give the universal selector the overscroll-behavior-y: contain property in our index.css file, and that will be all:

* {
  overscroll-behavior-y: contain;
}

Project overview

The reason we invest a lot of time in development is to ensure that our end users are completely satisfied. The pull-to-refresh gesture is intended to remain consistent throughout. Although there may be a demand for uniqueness in the animation and SVG style, the pull-to-refresh gesture itself should allow users to refresh whenever they feel the need to do so.

This article will focus on the implementation of custom pull-to-refresh gestures with React and overscroll behavior in CSS. The custom pull-to-refresh gesture should be something that appears at the top of our mobile browser and should only be visible when the user scrolls past the page borders.

Prerequisites

You must satisfy these requirements to follow this article properly:

  • A copy of Node.js installed
  • Fundamental knowledge of JavaScript syntax
  • A working grasp of React and React Hooks
  • Familiarity with the CLI and its commands
  • Familiarity with Tailwind CSS

Setting up our development area

To get our development area up and running, open up your terminal and run the commands:

cd Desktop
npx create-react-app pull-to-refresh
npm start

To install Tailwind, follow these steps from the documentation. At this point, our application should be up and running, and you should clear the app.js file for a clean start.

Implementing pull-to-refresh with React

To be able to implement the pull-to-refresh gesture, we must first acknowledge that this gesture will not be complete until there is first a touch; then a swipe downward, which is the drag; then an end to the drag. These actions are implemented with what we’ll refer to as EventListeners, which will be called whenever an action is delivered to the browser window.

There are a couple of different EventListeners, but only three will get the job done. Those three are:

  1. touchstart
  2. touchmove
  3. touchend

touchstart is only going to trigger when it senses the first touch. touchmove will trigger when the touch is followed by a move or drag. Lastly, touchend will trigger when there are no more touches registered in the window/on screen.

The useEffect Hook will be responsible for adding and removing our event listeners. After importing useEffect(), we can copy and paste the code below into our App.js file:

// add and remove event listeners
  useEffect(() => {
    window.addEventListener("touchstart", pullStart);
    window.addEventListener("touchmove", pull);
    window.addEventListener("touchend", endPull);
    return () => {
      window.removeEventListener("touchstart", pullStart);
      window.removeEventListener("touchmove", pull);
      window.removeEventListener("touchend", endPull);
    };
  });

Since we are dealing with a pull gesture, there will inevitably be a touch to trigger the pull gesture. Therefore, our first concern is to have a state to store the user’s first touch towards the top of the screen.

Setting the startPoint and pullChange

First things first, we will set the startPoint, which is a state to hold the startPoint’s screenY value when the user’s initial touch is registered.

To create the state, we will need to first import useState() and declare a state using the Hook.

  /**
    state to hold the start point
  */
    const [startPoint, setStartPoint] = useState(0);

The next thing we’ll do is create a state that will calculate how far the user has pulled the screen down — this will be the state that holds the change from the start point to the current point. The pull change will be equivalent to current point - start point; hence, we will refer to this state as the pullChange. The current point is where the user has been able to drag from the startPoint.



/**
   * 
    state to hold the change in the start point and current point
    the pull change is equivalent to current point - start point
  */
  const [pullChange, setPullChange] = useState();

We will then need a ref to target the .Refresh-container element in the DOM. We can implement this by first importing useRef() from React and declaring it, as seen below:

 /**
    ref to target the `.refresh-container` element in the DOM
   */
    const refreshCont = useRef(0);

Forcing a refresh

We also need a function to initialize loading and force the page to refresh within a second. This is the right time to create the function called initLoading, which adds the .loading class to the refresh container element to signify the loading state. We’ll also need a setTimeout function, which will reload the page after 1000ms.

const initLoading = () => {
  refreshCont.current.classList.add("loading");
  setTimeout(() => {
    window.location.reload();
  }, 1000);
};

Now, we need a function to listen to the start point state and handle our touchstart event. We will call this function pullStart. This function gets the start point of the user touch gesture and the pull start, which only runs the first time you touch the screen — so the function is very useful for getting the start position.

const pullStart = (e) => {
  const { screenY } = e.targetTouches[0];
  setStartPoint(screenY);
};

As seen in the useEffect() Hook above, the pull function will run when your finger moves on the screen. As you move from the top down to the middle of the screen, it calculates the difference between the current position and the starting position and saves it to our pullChange function.

const pull = (e) => {
  /**
   * get the current user touch event data
   */
  const touch = e.targetTouches[0];
  /**
   * get the touch position on the screen's Y axis
   */
  const { screenY } = touch;
  /**
   * The length of the pull
   *
   * if the start touch position is lesser than the current touch position, calculate the difference, which gives the `pullLength`
   *
   * This tells us how much the user has pulled
   */
  let pullLength = startPoint < screenY ? Math.abs(screenY - startPoint) : 0;
  setPullChange(pullLength);
  console.log({ screenY, startPoint, pullLength, pullChange });
};

As seen in useEffect above, endPull is a function that runs at the end of the touch gesture.

, endPull = (e) => {
  setStartPoint(0);
  setPullChange(0);
  if (pullChange > 220) initLoading();
};

// function to reset the refresh button and start the loading by running the `initLoading()` function when `pullChange` has passed a certain threshold

Styling with Tailwind CSS and writing logic

Below, we styled the DOM with Tailwind. We have our ref in the div container, our margin-top will be assigned to pullChange(), and it is divided by an arbitrary value, which makes the drag slower than normal.

The margin-top is responsible for pushing our refresh container down. In our SVG, we also have our spinner. After styling our spinner, we see that the rotate function goes along with pullChange: the spinner rotates as it is being pulled, and by the time it is released, it will add the .loading class.

//Before Tailwind
 <div
        ref={refreshCont}
        className=""
        style={{ marginTop: pullChange / 3.118 || "" }}
      >
        <div className="">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            strokeWidth={1.5}
            stroke="currentColor"
            className=""
            style={{ transform: `rotate(${pullChange}deg)` }}
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
            />
          </svg>
        </div>
      </div>
      <div className="">
        <header className="">
          <h1 className="">Welcome to my app!</h1>
          <p>Pull down to refresh</p>
        </header>
      </div>

Spinner Rotating After Pull-to-refresh

//After Tailwind
<div
ref={refreshCont}
className="refresh-container w-fit -mt-10 m-auto"
style={{ marginTop: pullChange / 3.118 || "" }}
>
<div className="refresh-icon p-2 rounded-full">
  <svg
    xmlns="http://www.w3.org/2000/svg"
    fill="none"
    viewBox="0 0 24 24"
    strokeWidth={1.5}
    stroke="currentColor"
    className="w-6 h-6"
    style={{ transform: `rotate(${pullChange}deg)` }}
  >
    <path
      strokeLinecap="round"
      strokeLinejoin="round"
      d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
    />
  </svg>
</div>
</div>
<div className="body flex justify-center items-center min-h-screen">
<header className="flex flex-col text-center">
  <h1 className="text-4xl font-bold">Welcome to my app!</h1>
  <p>Pull down to refresh</p>
</header>
</div>

If every step was carefully and consciously followed, we should arrive at a result that looks just like the GIF below:

The Demo App Showcasing a Pull-to-refresh Feature

Conclusion

The gesture is crucial because it adds to the long list of features that let users interact fully with an application. In this tutorial, we went over how to implement pull-to-refresh step by step; it was a quick, seamless process that resulted in a job well done. I’d like to take this opportunity to express my gratitude for sticking with me this far.

LogRocket: Full visibility into your production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket combines session replay, product analytics, and error tracking – empowering software teams to create the ideal web and mobile product experience. What does that mean for you?

Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay problems as if they happened in your own browser to quickly understand what went wrong.

No more noisy alerting. Smart error tracking lets you triage and categorize issues, then learns from this. Get notified of impactful user issues, not false positives. Less alerts, way more useful signal.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Emmanuel Odioko I am a frontend technology enthusiast, intrigued by frameworks and how they work, presently specializing in React, and would love to teach on any topic under it.

Leave a Reply