Some Problems I Face When Building React Single Page Application And How I Solve Them
Tags: javascript, react, debounce, reactrouter
Published: 2023-03-03 13:44:46

### **Going To Top Of Page On Route Change**

By default, the React Router will not take you to the top of a new component after a route change, rather the page position will remain like before it navigated to the new page. To solve this, we will create `ScrollToTop` Javascript file in a helper folder `helpers` in the `src` folder and add the codes below.

```javascript
import { useEffect } from "react"
import { useLocation } from "react-router-dom"

const ScrollToTop = () => {
  const { pathname } = useLocation()

  useEffect(() => {
    window.scrollTo(0, 0)
  }, [pathname])

  return null
}

export default ScrollToTop
```

We destructure the `pathname` of the current location from the `useLocation()` hook and added a `useEffect()` hook in the component. The `useEffect()` hook has the `pathname` as its dependency. On every page change, the page will be forced to scroll to the top.

This piece of instruction will be used in the `App` component like so:

```javascript
import React from "react"
import { BrowserRouter } from "react-router-dom"
import ScrollToTop from "./helpers/ScrollToTop";

function App() {
  return (
    <BrowserRouter>
      <ScrollToTop />
      <App />
    </BrowserRouter>
  );
}
```

### **Using Denounce**

Debouncing is a programming pattern in which the execution of a certain function that performs an expensive or time-consuming operation is delayed for a particular time, usually in seconds, to improve the application's performance.

It takes a delay parameter that determines how long the debounce function will wait before performing a task. Such tasks could include searching through a list or most importantly, performing an asynchronous request.

It is commonly used to limit the number of unnecessary calls to an API when the user is still typing.

Unlike in Vue, debounce is not so straightforward to use in React. There are a lot of solutions out there, some of which use hooks like `useMemo` and `useCallback`.

This is how I have been able to take on this problem:

Let's create a `useDebounce` file in the `helpers` folder.

```javascript
import { useState, useEffect } from "react"

const useDebounce = (value, delay) => {
  const [debouncedValue, setDebouncedValue] = useState(value)

  useEffect(
    () => {
      const handler = setTimeout(() => {
        setDebouncedValue(value)
     }, delay)
      return () => {
        clearTimeout(handler)
      }
    },
    [value, delay]
  )
  return debouncedValue
}

export default useDebounce
```

* We created a `useDebounce` function that takes two parameters, `value` an `delay`. This is the value we want to debounce and how long we want to delay before changing this value in milliseconds.
    
* The `value` is then stored in a `useState` hook.
    
* The `useEffect` hook takes in a `setTimeout` function that delays according to the value of `delay` parameter before changing the `value`.
    
* `delay` and `value` parameters are also the `useEffect` dependencies.
    
* After this, the `setTimeout` is cleared or aborted, `useEffect` is also exited.
    
* The `useEffect` hook will only run again when one or both of its dependencies change.
    

Below is a use case of the `useDebounce` function.

```javascript
import React, { useEffect, useState  } from "react"
import useDebounce from "./utils/useDebounce"

const Universities = () => {
  const [data, setData] = useState([])
  const [term, setTerm] = useState("")
  const debouncedTerm = useDebounce(term, 500)

  const search = name => {
    fetch(`http://universities.hipolabs.com/search?name=${name}`)
      .then(res => res.json())
      .then(data => {
        setData(data)
        console.log(data)
      })
  }
  const clear = () => {
    setData([])
    setTerm("")
  }
  useEffect(() => {
    search(debouncedTerm)
  }, [debouncedTerm])

  return (
    <div>
      <div className="relative w-[98%] mx-auto">
        <input
          type="text"
          autoComplete="off"
          onChange={e => setTerm(e.target.value)}
          value={term}
          placeholder="Search movies"
          style={{
            backgroundColor: " rgb(229 231 235)",
            color: "rgb(17 24 39)",
            width: "100%",
            padding: "1rem",
          }}
        />
        <button onClick={() => clear()}>
          <svg
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            strokeWidth={1.5}
            stroke="currentColor"
            style={{ cursor: "pointer", width: "5rem", height: "5rem" }}
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              d="M6 18L18 6M6 6l12 12"
            />
          </svg>
        </button>
      </div>
      {data.map((item, index) => (
        <div key={index}>{item.name}</div>
      ))}
     Demonstrating debounce with asynchronous call for a university api.
    </div>
  )
}
```

We debounced the `term` state using the `useDebounce` function we created earlier and used the `debouncedTerm` in both the `search` function and th `useEffect` array dependency.

### **Protecting Routes**

Unlike Vue, React does not provide an out-of-the-box solution for the protection of routes against users that do not have certain authentication and authorization, for example, guests.

To demonstrate this, I am going to use the context API to create an `auth` state and a `toggleAuth` function that mimics the login and logout of a user that can be used anywhere in the application.

```javascript
//AuthContext.js
import React, { createContext, useState } from "react"

export const AuthContext = createContext()

const AuthContextProvider = ({ children }) => {
  const [auth, setAuth] = useState(false)

  const toggleAuth = () => setAuth(!auth)

  return (
    <AuthContext.Provider value={{ auth, toggleAuth }}>
      {children}
    </AuthContext.Provider>
  )
}

export default AuthContextProvider
```

We now have to create a `ProtectedRoutes` file. In this file, we extract the `auth` state and determine whether to display the routes or navigate the user to a login page based on the value of `auth`, that is, if it is `true` or `false`.

*An* `<Outlet>` *should be used in parent route elements to render their child route elements. This allows nested UI to show up when child routes are rendered. If the parent route matched exactly, it will render a child index route or nothing if there is no index route.*

*&lt;Outlet&gt; is a special component that is used to render the child components of a parent route. It acts as a placeholder that allows the child components to be rendered within the parent component.*

Don't forget to wrap your application with the `AuthContextProvider`.

```javascript
import React from "react"
import { Navigate, Outlet } from "react-router-dom"
import { AuthContext } from "../context/AuthContext"

const ProtectedRoutes = () => {
  const { auth } = useContext(AuthContext)

  return auth ? <Outlet /> : <Navigate to="/login" />
}

export default ProtectedRoutes
```

We can now use the protected route like so in the route definitions.

```javascript
<Route element={<ProtectedRoutes />}>
  <Route element={<Home />} path="/" exact />
  <Route element={<Products />} path="/products" />
</Route>
```

We provided the `ProtectedRoutes` file as the element component prop of the route component that will wrap the components that we want to protect.

### **Conclusion**

In conclusion, building a React single-page application can come with its own set of challenges. Some of these challenges may include protecting routes, using debounce, and going to the top of the page on route change. However, there are several solutions to overcome these problems.

Protecting routes can be solved using authentication and authorization techniques. This ensures that only authenticated users can access certain parts of the application. The use of debouncing helps to prevent performance issues and overload by reducing the number of function calls. Going to the top of the page on route change can be achieved by using the scrollTo window method.

By applying these solutions, developers can improve the functionality and user experience of their React SPA.