Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to share state between all instances of a React component?

Tags:

reactjs

Let's say I have a reusable component that, inside a useEffect, loads some data from an API, stores it inside a state which is then used by it in some way.

However, in some pages, this same component is reused in multiple places, making multiple calls for the same data.

So, I would like to have a cache of sorts, where I store the retrieved values and reuse it in other instances if it wasn't fetched too long ago.

I don't really want to store it in a global state like Redux or (partially) Context because this is just a small detail of a reusable component.

The only solution I could come up with was using an external variable as the cache:

import { useEffect, useState } from "react";

let cache = null;
let cacheFetchedTime = null;

export const MyDropdown = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      if (cacheFetchedTime && Date.now() - cacheFetchedTime.getTime() < 5000) {
        setData(cache);
        return;
      }

      const fetchedData = await fetchData();
      setData(fetchedData);
      cache = fetchedData;
      cacheFetchedTime = new Date();
    };

    fetchData();
  }, []);

  return (
    <div>
      {/* use `data` somehow */}
    </div>
  );
};

Is this the best (or only) way of doing this or is there a better alternative?

like image 493
Asghwor Avatar asked Oct 25 '25 02:10

Asghwor


1 Answers

I decided to keep my cache at the top-level of the file where the component is at.

More specifically, I created an HOC that receives the getter function and returns a function that caches its answer for x period of time:

/* createCachedCall.js */

const createCachedCall = ({ callback, ms = 5000 }) => {
  let ongoingAsyncCall = null
  let result = null
  let lastFetchDate = null

  return async () => {
    if (ongoingAsyncCall) return ongoingAsyncCall

    if (lastFetchDate && Date.now() - lastFetchDate.getTime() <= ms)
      return result

    ongoingAsyncCall = Promise.resolve(callback())
    result = await ongoingAsyncCall
    lastFetchDate = new Date()
    ongoingAsyncCall = null

    return result
  }
}


/* MyDropdown.jsx */

const getDataCached = createCachedCall({
  callback: getData,
  ms: 300000, // 5 minutes
})

export const MyDropdown = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const data = await getDataCached();
    };

    fetchData();
  }, []);

  return (
    <div>
      {/* use `data` somehow */}
    </div>
  );
};

like image 189
Asghwor Avatar answered Oct 26 '25 17:10

Asghwor