DEV Community

Adewale Azeez
Adewale Azeez

Posted on

Stateful React Pages using URLSearchParams and Local/Session Storage

Note: This article speaks to creating react web pages that can retain their active state after a refresh or link share through the use of URL params and react state.

Table of content

What is a stateful page

First, what does stateful mean? According to redhat stateful allows the users to store, record, and return already established information and processes over the internet. "Stateful applications and processes allow users to store, record, and return to already established information and processes over the internet". In the context of this article, stateful refers to the capability to store, record, and return information to a webpage after refreshing the page or the copied link to the page is open in a different browser or device.

A stateful page will be able to render the UI and components as the last time the page information and processes are recorded even if the page is open in another browser or device.

Advantages of a stateful page

  • An advantage of making your pages and app stateful is giving the user the ability to resume on the page from their last interaction.
  • Another reason to make the page stateful is to be able to share a URL with another user and they can get the exact presentation as shown on your end.
  • For a page with a lot of data presentation it makes it sane to have a link to that presentation as it is, so the link can be revisited and still get your page.
  • A stateful page can also allow auto-fill of form on a page even if the page refresh or internet connection is loss, hence the user can resume the filling of the from with their previously entered values still on the page.

Methods to a stateful page madness

There are various ways to make a webpage stateful, two of the methods are storing the page state information in the URL params or using a local storage to store the page's current information. Both methods are described below:

Using URLSearchParams

React Router DOM provides an intuitive way of managing the URL search value of a webpage, it provides the useSearchParams hook that allows accessing the webpage URL search values and also changing the values without reloading the active webpage.

The useSearchParams hook provides the two variables the first for retrieving the URL search values, and the other for adding or modifying the URL search values.

Setup the URLSearchParams

The useSearchParams hook accepts a default search params value in the form of a string, Record, or URLSearchParams which will be useful in retrieving data from the webpage URL

Install the react-router-dom package

npm i react-router-dom
Enter fullscreen mode Exit fullscreen mode

Initialize in your components

//...
import { useSearchParams } from "react-router-dom";
//...
const [searchParams, setSearchParams] = useSearchParams(window.location.search);
Enter fullscreen mode Exit fullscreen mode

Retrieving URL values

An example is a webpage with the search params value: ?page=1&name=user-one. The return value is either a string or null for a non-existence key.

const page = searchParams.get("page"); // 1
const name = searchParams.get("name"); // user-one
const size = searchParams.get("size"); // null
Enter fullscreen mode Exit fullscreen mode

Adding or Modifying the URL values

The second value from the useSearchParams hooks the URL search values can be modified to add a new search param or remove or modify existing search params.

searchParams.delete("page"); // => ?name=user-one
searchParams.set("size", 10); // => ?name=user-one&size=10
searchParams.append("status",["APPROVED", "REJECTED"]); // => ?name=user-one&size=10&status=APPROVED&status=REJECTED
Enter fullscreen mode Exit fullscreen mode

Retrieving values from URLSearchParams to restore state

// UrlParamsExamplePage.tsx
import React from 'react';
import { useSearchParams } from "react-router-dom";
import { Helpers } from './Helpers';

function UrlParamsExamplePage() {
  const [searchParams, setSearchParams] = useSearchParams(window.location.search);
  const [page, setPage] = React.useState<number>(parseInt(searchParams.get("page") ?? "0"));

  return (
    <div style={{ display: "flex", flexDirection: "column", width: "fit-content" }}>
      <span>Page is {page}</span>
      <button type='button' onClick={() => Helpers.updateSearchParams("page", page+1, setSearchParams, () => {
        setPage(page+1);
      })}>Next Page</button>
      <br/>
    </div>
  );
}

export default UrlParamsExamplePage;

Enter fullscreen mode Exit fullscreen mode

Full source code at UrlParamsExamplePage.tsx

Using Local or Session Storage

The browser local or session storage can also be used to store a webpage state. The Browser's local storage and session storage implementations provide API to store, retrieve, delete and modify data for a website. Local storage is different from session storage in the sense that session storage only retains the stored data in that browser tab while local storage retains the data across tabs for that website, but both share identical APIs.

The local and session storage does not require any setup as it is readily available in the JavaScript global window object.

Adding value to storage

const storage = window.localStorage ?? window.sessionStorage;
storage.setItem("page", "1");
storage.setItem("name", "user-one");
Enter fullscreen mode Exit fullscreen mode

Retrieving values from the Storage API

The return value is either a string or null for a non-existence key.

const storage = window.localStorage ?? window.sessionStorage;
const page = storage.getItem("page"); // 1
const name = storage.getItem("name"); // user-one
const size = storage.getItem("size"); // null
Enter fullscreen mode Exit fullscreen mode

Removing or modifying values in storage

The storage API provides the removeItem method to remove a key value, and also the clear method to remove all the entries for that website.

const storage = window.localStorage ?? window.sessionStorage;
storage.removeItem("page"); // delete a single entry
storage.clear();            // remove all entries for the website
Enter fullscreen mode Exit fullscreen mode

Retrieving values from Storage to restore state

// StorageExamplePage.tsx
import React from 'react';

function StorageExamplePage() {
  const storage = window.localStorage ?? window.sessionStorage;
  const [page, setPage] = React.useState<number>(parseInt(storage.getItem("page") ?? "0"));

  return (
    <form style={{ display: "flex", flexDirection: "column", width: "fit-content" }}>
      <input placeholder="Name" defaultValue={storage.getItem("name") ?? ""} onChange={(e: any) => {
        storage.setItem("name", e.target.value);
      }}/>
      <span>Page is {page}</span>
      <button type='button' onClick={() => {
        storage.setItem("page", ""+(page-1));
        setPage(page-1);
      }}>Prev Page</button>
      <button type='button' onClick={() => {
        storage.setItem("page", ""+(page+1));
        setPage(page+1);
      }}>Next Page</button>
    </form>
  );
}

export default StorageExamplePage;
Enter fullscreen mode Exit fullscreen mode

Full source code at StorageExamplePage.tsx

Alternatives state storage methods

Other alternatives to store data to be used to restore React webpages to React native mobile page states are.

Cache: The browser cache can store data with expiry time and can also share the data with other websites if needed.
Mobile - Async Storage: React native provides an async storage library that can be used to store data between app closing and reopening.
Mobile - SQLite: Since the mobile app has access to IO operation on the persistence storage, compact DB like SQLite will be helpful in quick storage and retrieval of data for page state restoration.
Mobile - File IO: Another alternative for mobile is to use the mobile native file access capability to create a custom file on the device to store state data, and read the file to retrieve the state data.

Helper implementations for URLSearchParams

Typescript's functions to manage URL params and examples on how to use the functions

Functions to manage URL search params

import { SetURLSearchParams } from "react-router-dom";
// ...
export type NoseurObject<T> = { [key: string]: T; };
export type URLSearchParamsValue = string | string[] | number | number[] | undefined | null;
// ...
function updateSearchParams(key: string, value: URLSearchParamsValue, setSearchParams: SetURLSearchParams | URLSearchParams, cb?: Function | undefined) {
    const fun = (prev: URLSearchParams) => {
        if (value === undefined || value === null) {
            prev.delete(key);
        } else if (Array.isArray(value)) {
            prev.delete(key);
            for (const v of value) prev.append(key, `${v}`);
        } else {
            prev.set(key, `${value}`);
        }
        return prev;
    };
    if (setSearchParams instanceof URLSearchParams) fun(setSearchParams);
    else setSearchParams(fun);
    cb && cb();
}

function normalizeUrlParams(urlSearchParams: URLSearchParams, keyLookupMap: NoseurObject<string> = {}, extraParams: NoseurObject<string | number> = {}) {
    const params: NoseurObject<any> = {};
    Object.keys(Object.fromEntries(urlSearchParams)).forEach((key: string) => {
        const lookupKey = keyLookupMap[key] ?? key;
        const values = urlSearchParams.getAll(key);
        if (values.length) {
            params[lookupKey] = values.length > 1 ? values : values[0];
        }
    });
    Object.keys(extraParams).forEach((key) => params[key] = extraParams[key])
    return params;
}

function urlParamsToSearch(urlSearchParams: URLSearchParams, only?: string[], includes?: NoseurObject<URLSearchParamsValue>) {
    only && only.length && Object.keys(Object.fromEntries(urlSearchParams)).forEach((key: string) => {
        if (!only.includes(key)) urlSearchParams.delete(key);
    });
    if (includes) {
        Object.keys(includes).forEach((key: string) => updateSearchParams(key, includes[key], urlSearchParams));
    }
    return urlSearchParams.toString();
}
Enter fullscreen mode Exit fullscreen mode

Usage Examples

import { useSearchParams } from "react-router-dom";
//...
const [searchParams, setSearchParams] = useSearchParams(window.location.search);
Enter fullscreen mode Exit fullscreen mode

Add some params entries

updateSearchParams("page", 1, setSearchParams);
updateSearchParams("name", "user-one", setSearchParams);
updateSearchParams("status", ["APPROVED", "REJECTED"], setSearchParams);
Enter fullscreen mode Exit fullscreen mode

Invoke a callback after the URL has been updated

updateSearchParams("page", 2, setSearchParams, () => {
    // e.g. fetch data from page 2
});
Enter fullscreen mode Exit fullscreen mode

Delete an entry from the URL params

updateSearchParams("name", null, setSearchParams);
updateSearchParams("name", undefined, setSearchParams);
Enter fullscreen mode Exit fullscreen mode

URLSearchParams to JSON object

let search;
search = normalizeUrlParams(searchParams); // { "page": 2, "status": ["APPROVED", "REJECTED"] }

// with key lookup map
search = normalizeUrlParams(searchParams, { "page": "users_page" }); // { "users_page": 2, "status": ["APPROVED", "REJECTED"] }

// with extra params
search = normalizeUrlParams(searchParams, {}, {"size": 20, "name": "one"}); // { "page": 2, "status": ["APPROVED", "REJECTED"], "size": 20, "name": "one" }
Enter fullscreen mode Exit fullscreen mode

URLSearchParams to URL search string

let searchString;
searchString = urlParamsToSearch(searchParams) // page=2&status=APPROVED&status=REJECTED&name=user-one

// only selected entries
searchString = urlParamsToSearch(searchParams, ["page", "name"]) // page=2&name=user-one

// include extra entry values
searchString = urlParamsToSearch(searchParams, [], {"size": 20}) // page=2&status=APPROVED&status=REJECTED&name=user-one&size=20

Enter fullscreen mode Exit fullscreen mode

Glossary

  • API: Application Programming Interface, a method, routine, or procedure exposed by a component to allow usage or modification of its functionalities.
  • Stateful: The capability of a presentation or system to store, record, and return the system to its previous state.
  • Stateless: The absence of the capability of a presentation or system to retain information from previous interactions.

References

The sample project of this article is hosted at https://quickutils.github.io/stateful-ui/ and the source code at https://github.com/quickutils/stateful-ui

Top comments (0)