How to save states between route change in React

Viewed 457

I have a simple application that shows the list of local hotels. Each list item has a <Link/> that redirects to another component, which displays the location on the map for that specific hotel. When switching routes, it seems that the <ProductList/> component gets destroyed and so do all the states in it. So every time when it makes new API calls and re-renders. I tried to save in local storage on each componentWillUnmount and retrieve it in useEffect() so that I can make API calls conditionally, and it works but sometimes doesn't work.

import React, { useState, useEffect} from "react";
import ProductItem from "../Components/ProductItem";
import axios from "axios";

const ProductList = () => {
  const [hotelList, setHotelList] = useState([]);

  // Get user location by IP
  const getCurrentLocation = () => {
    return fetch("https://ipinfo.io/json?token=MyToken").then(
      (response) => response.json()
    );
  };

  // Get list of hotels in specific location
  const getHotelsInLocation = (destInfo) => {
    console.log('destInfo is: ', destInfo)
    const options = {
      method: "GET",
      url: "https://booking-com.p.rapidapi.com/v1/hotels/search",
      params: {
        checkout_date: "2022-10-01",
        units: "metric",
        dest_id: destInfo.destId,
        dest_type: destInfo.destType,
        locale: "en-gb",
        adults_number: 2,
        order_by: "popularity",
        filter_by_currency: "USD",
        checkin_date: "2022-09-30",
        room_number: 1,
      },
      headers: {
        "X-RapidAPI-Host": "booking-com.p.rapidapi.com",
        "X-RapidAPI-Key": "MyApiKey",
      },
    };
    axios
      .request(options)
      .then(function (response) {
        console.log(response.data.result);
        setHotelList(response.data.result);
      })
      .catch(function (error) {
        console.error(error);
      });
  };

  useEffect(() => {
    getCurrentLocation().then((currentLocation) => {
      console.log("Current city ", currentLocation.city);
      const options = {
        method: "GET",
        url: "https://booking-com.p.rapidapi.com/v1/hotels/locations",
        params: { locale: "en-gb", name: currentLocation.city },
        headers: {
          "X-RapidAPI-Host": "booking-com.p.rapidapi.com",
          "X-RapidAPI-Key":
            "MyApiKey",
        },
      };
      axios
        .request(options)
        .then(function (response) {
          console.log(response.data);
          let destId = response.data[0].dest_id;
          let destType = response.data[0].dest_type;
          const destInfo = { destId, destType };
          getHotelsInLocation(destInfo);
        })
        .catch(function (error) {
          console.error(error);
        });
    });
  }, []);

  return (
    <>
      {hotelList.map((hotel) => (
        <ProductItem key={hotel.hotel_id} hotel={hotel} />
      ))}
    </>
  );
};

export default ProductList;

How could I do so when coming back to <ProductList/> component, it doesn't make new API calls but just display the hotelList from the previous call.

3 Answers

In this case, You need to keep the datas in the centralized store. For this, you have 2 options. One is using context api which is default react feature. Another one is using redux which is seperate package.

My opinion is, you can go with context api.

A simple example of context api is given below,

Filecontext.jsx

import { createContext } from 'react'

export const Filecontext = createContext({});

Formcomponent.jsx

import { Filecontext } from '../Contexts/Filecontext';
import { useContext } from 'react'

export default function Formcomponent() {
    const { setName, setEmail, setMobileno, showAlert } = useContext(Filecontext)

    return (
        <>
            <div className="form-group">
                <label>Name : </label>
                <input type="text" onChange={(e) => { setName(e.target.value) }} />
            </div>
            <div className="form-group">
                <label>Email : </label>
                <input type="email" onChange={(e) => { setEmail(e.target.value) }} />
            </div>
            <div className="form-group">
                <label>Mobile No : </label>
                <input type="number" onChange={(e) => { setMobileno(e.target.value) }} />
            </div>
            <div className="form-group">
                <input type="submit" value="submit" onClick={() => { showAlert() }} />
            </div>
        </>
    )
}

Listcomponent.jsx

import { Filecontext } from '../Contexts/Filecontext';
import { useContext } from 'react'

export default function Listcomponent() {
    const { name, email, mobileno } = useContext(Filecontext)
    return (
        <>
            <p>Name :</p>{name}
            <p>Email :</p>{email}
            <p>Mobile No :</p>{mobileno}
        </>
    )
}

App.js

import logo from './logo.svg';
import './App.css';
import Formcomponent from './Components/Formcomponent';
import Listcomponent from './Components/Listcomponent';

import { Filecontext } from './Contexts/Filecontext';
import { useState } from 'react'

function App() {
  const [name, setName] = useState("")
  const [email, setEmail] = useState("")
  const [mobileno, setMobileno] = useState("")

  return (
    <div className="App">
      <Filecontext.Provider value={{ name, setName, email, setEmail, mobileno, setMobileno, showAlert }}>
        <Formcomponent />
        <Listcomponent />
      </Filecontext.Provider>

    </div>
  );
}

export default App;

From the above example, The Formcomponent can be displayed in different route and Listcomponent can be displayed in different route. But even then, the datas can be retained with the help of context.

Although caching with useEffect is possible, My recommendation is to consider using one of the query caching libraries such as:

From my experience if you're already using redux toolkit RTK Query will be the fit for you.

The short answer is that

  • You need the list of hotels in a global state instead of local.
  • Use context / redux along with a cache policy in API calls and track your state changes.
  • Skip the API calls based on your application logic and memoize the query result when needed hence in effect memoize the global state.
Related