React Suspense Resource Deduplication: Preventing Duplicate Requests | MLOG | MLOG

Now, the UserResource checks if a resource already exists in the cache. If it does, the cached resource is returned. Otherwise, a new request is initiated, and the resulting promise is stored in the cache. This ensures that only one request is made for each unique userId.

2. Using a Dedicated Caching Library (e.g., `lru-cache`)

For more complex caching scenarios, consider using a dedicated caching library like lru-cache or similar. These libraries provide features like cache eviction based on Least Recently Used (LRU) or other policies, which can be crucial for managing memory usage, especially when dealing with a large number of resources.

First, install the library:

            
npm install lru-cache

            

Then, integrate it into your UserResource:

            
import React, { Suspense } from 'react';
import LRUCache from 'lru-cache';

const fetchUser = (userId) => {
  console.log(`Fetching user with ID: ${userId}`); // Simulate network request
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({ id: userId, name: `User ${userId}`, email: `user${userId}@example.com` });
    }, 1000); // Simulate network latency
  });
};

const cache = new LRUCache({
  max: 100, // Maximum number of items in the cache
  ttl: 60000, // Time-to-live in milliseconds (1 minute)
});

const UserResource = (userId) => {
  if (!cache.has(userId)) {
    let promise = null;
    let status = 'pending'; // pending, success, error
    let result;

    const suspender = fetchUser(userId).then(
      (r) => {
        status = 'success';
        result = r;
        cache.set(userId, {
          read() {
            return result;
          },
        });
      },
      (e) => {
        status = 'error';
        result = e;
        cache.set(userId, {
          read() {
            throw result;
          },
        });
      }
    );

    cache.set(userId, {
        read() {
            if (status === 'pending') {
                throw suspender;
            } else if (status === 'error') {
                throw result;
            }
            return result;
        }
    });
  }

  return cache.get(userId);
};


const UserProfile = ({ userId }) => {
  const user = UserResource(userId).read();
  return (
    

User Profile

ID: {user.id}

Name: {user.name}

Email: {user.email}

); }; const UserDetails = ({ userId }) => { const user = UserResource(userId).read(); return (

User Details

ID: {user.id}

Name: {user.name}

); }; const App = () => { return ( Loading...
}> ); }; export default App;

This approach provides more control over the cache's size and expiration policy.

3. Request Coalescing with Libraries like `axios-extensions`

Libraries like axios-extensions offer more advanced features such as request coalescing. Request coalescing combines multiple identical requests into a single request, further optimizing network usage. This is particularly useful in scenarios where requests are initiated very close to each other in time.

First, install the library:

            
npm install axios axios-extensions

            

Then, configure Axios with the cache adapter provided by axios-extensions.

Example using `axios-extensions` and creating a resource:

            
import React, { Suspense } from 'react';
import axios from 'axios';
import { cacheAdapterEnhancer, throttleAdapterEnhancer } from 'axios-extensions';

const instance = axios.create({
  baseURL: 'https://api.example.com', // Replace with your API endpoint
  adapter: cacheAdapterEnhancer(axios.defaults.adapter, { enabledByDefault: true }),
});

const fetchUser = async (userId) => {
  console.log(`Fetching user with ID: ${userId}`); // Simulate network request
  const response = await instance.get(`/users/${userId}`);
  return response.data;
};


const UserResource = (userId) => {
    let promise = null;
    let status = 'pending'; // pending, success, error
    let result;

    const suspender = fetchUser(userId).then(
        (r) => {
            status = 'success';
            result = r;
        },
        (e) => {
            status = 'error';
            result = e;
        }
    );

    return {
        read() {
            if (status === 'pending') {
                throw suspender;
            } else if (status === 'error') {
                throw result;
            }
            return result;
        },
    };
};


const UserProfile = ({ userId }) => {
  const user = UserResource(userId).read();
  return (
    

User Profile

ID: {user.id}

Name: {user.name}

Email: {user.email}

); }; const UserDetails = ({ userId }) => { const user = UserResource(userId).read(); return (

User Details

ID: {user.id}

Name: {user.name}

); }; const App = () => { return ( Loading...
}> ); }; export default App;

This configures Axios to use a cache adapter, automatically caching responses based on the request configuration. The cacheAdapterEnhancer function provides options for configuring the cache, such as setting a maximum cache size or expiration time. throttleAdapterEnhancer can also be used to limit the number of requests made to the server within a certain time period, further optimizing performance.

Best Practices for Resource Deduplication

Global Considerations for Data Fetching and Deduplication

When designing data fetching strategies for a global audience, several factors come into play:

For example, a travel booking website targeting a global audience might use a CDN to serve flight and hotel availability data from servers located in different regions. The website would also use a currency conversion API to display prices in the user's local currency and provide options for filtering search results based on language preferences.

Conclusion

Resource deduplication is an essential optimization technique for React applications using Suspense. By preventing duplicate data fetching requests, you can significantly improve performance, reduce server load, and enhance the user experience. Whether you choose to implement a simple promise cache or leverage more advanced libraries like lru-cache or axios-extensions, the key is to understand the underlying principles and choose the solution that best fits your specific needs. Remember to consider global factors like CDNs, localization, and accessibility when designing your data fetching strategies for a diverse audience. By implementing these best practices, you can build faster, more efficient, and more user-friendly React applications.