Save the previous value of a variable
Tags: react, tutorial, javascript, webdev
Published: 2023-12-09 08:37:54

In our previous post, we learned about the `useRef()` hook, which helps us [persist values](https://phuoc.ng/collection/react-ref/persist-values-between-renders/) to avoid unnecessary re-renders. Today, we'll delve into another practical application of `useRef()`: tracking the previous value of a variable.

Tracking the previous value of a variable can be useful in many ways. For instance, it can help us detect changes in state and trigger certain actions accordingly. It can also aid in comparing the current value with the previous value to determine if there have been any changes.

This is especially handy when implementing undo/redo functionality, where you need to compare the current value of something to its previous value. By being able to easily access the previous value, we can compare it with the current value and determine if any changes have been made.

Moreover, tracking the previous value can be helpful when dealing with form inputs or user interactions. By keeping track of the previous value, we can easily revert back to it if needed or validate against it.

Overall, there are numerous scenarios where tracking the previous value of a variable can come in handy, making our code more robust and efficient.

## Keeping track of previous values with a custom hook

We'll create a custom hook called `usePrevious` to keep track of the previous value of a state in React.

This hook uses a ref to store the previous value, which gets updated every time the component re-renders. Here's an example implementation of the `usePrevious` hook:

```ts
import * as React from 'react';

const usePrevious = (value) => {
    const ref = React.useRef();
    React.useEffect(() => {
        ref.current = value;
    });
    return ref.current;
};
```

In this example, we're creating a new ref with `useRef` to store the previous value. Then, we're using `useEffect` to update this ref every time the component re-renders. Finally, we're returning the current value of the ref using `return ref.current`.

By using this hook in your components, you can easily access and compare previous values without the hassle of manually storing them yourself. This can greatly simplify your code and make it easier to understand.

## Discovering scroll direction

Let's explore some useful applications of the `usePrevious` hook. First, we'll discuss detecting the scroll direction.

Detecting scroll direction is commonly used for infinite scrolling. By knowing whether the user is scrolling up or down, we can fetch data accordingly and append it to the existing list. This boosts performance by reducing the amount of data that needs to be loaded at once.

Another use case is for scroll-based animations. You may want to trigger an animation when a certain element comes into view while scrolling down, but not when scrolling up. By detecting the scroll direction and position, you can easily determine when to trigger the animation.

Additionally, detecting scroll direction can be useful in creating custom navigation menus that change based on the user's scroll position. For example, you could have a menu that appears at the top of the page when scrolling up and disappears when scrolling down, providing a more immersive user experience.

Overall, detecting scroll direction can be creatively used in many ways to enhance your web applications.

To track changes in the scroll position, we can use the `usePrevious` hook. This hook helps us keep track of the previous value of the scroll position, so we can determine whether it has increased or decreased. To get started, we need to create a state variable that will hold our current scroll position.

```ts
const [scrollPosition, setScrollPosition] = React.useState(0);
```

To figure out where the user is currently scrolling, we need to handle the `scroll` event. In our example, we create a function called `handleScroll` that updates the `scrollPosition` state variable with the current value of `window.scrollY`.

```ts
const handleScroll = () => setScrollPosition(window.scrollY);
```

Next, we use the `useEffect` hook to add an event listener for the `scroll` event. This listener calls our `handleScroll` function each time the event occurs. We also remove the listener when our component unmounts to avoid any memory leaks.

```ts
React.useEffect(() => {
    document.addEventListener("scroll", handleScroll);
    return () => {
        document.removeEventListener("scroll", handleScroll);
    };
}, []);
```

> **Good practice**
>
> To improve the performance of your scroll handler, it's recommended to use either debounce or throttle. This is because the `scroll` event can fire multiple times per second, which can slow down your website or app if your handler function is doing a lot of work.
>
> [Debouncing and throttling](https://phuoc.ng/collection/this-vs-that/debouncing-vs-throttling/) are techniques that limit the number of times a function is called based on a certain time
interval. Debouncing waits until a certain amount of time has passed since the last time the function was called before calling it again. Throttling, on the other hand, only calls the function once every set interval. These techniques can help ensure that your scroll handler is efficient and doesn't negatively impact the user experience.
>
> In our example, we'll use the `throttle` function from the lodash library to control the frequency of our `handleScroll` function.
>
> ```ts
> import { throttle } from 'lodash';
>
> const handleScrollThrottled = throttle(handleScroll, 100);
>
> React.useEffect(() => {
>     document.addEventListener("scroll", handleScrollThrottled);
>     return () => {
>         document.removeEventListener("scroll", handleScrollThrottled);
>     };
> }, []);
> ```
>
> In this example, we import the `throttle` function from lodash to optimize our scroll event handling. We create a new function, `handleScrollThrottled`, by passing in our original `handleScroll` function and an interval of 100 milliseconds. Then, we add an event listener for the `scroll` event using our new throttled function. Finally, we remove the listener when our component unmounts.
>
> By using throttling or debouncing in your scroll handlers, you can significantly improve your component's performance, reducing unnecessary re-renders and making your user experience smoother.

Next, we can use the `usePrevious` hook to retrieve the previous value of our scroll position.

```ts
const previousScrollPosition = usePrevious(scrollPosition);
```

Now that we've stored both our current and previous values as variables, we can compare them in an effect hook that runs every time the current value changes.

```ts
React.useEffect(() => {
    if (previousScrollPosition < scrollPosition) {
        console.log("Scroll down");
    } else if (previousScrollPosition > scrollPosition) {
        console.log("Scroll up");
    }
}, [scrollPosition, previousScrollPosition]);
```

In this example, we log "Scroll down" when the current scroll position is greater than the previous one, and "Scroll up" when it's less. By using this technique, we can quickly detect the scrolling direction and perform actions accordingly.

---

If you found this series helpful, please consider giving the [repository](https://github.com/phuocng/master-of-react-ref) a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

If you want more helpful content like this, feel free to follow me:

-   [Twitter](https://twitter.com/_phuocng)
-   [GitHub](https://github.com/phuocng)