hattenn
hattenn

Reputation: 4399

Caching a JSON response using ETag in a React Native app

What is the best way to implement the following scenario in a React Native app?

  1. Make an HTTP request to the server, get a JSON response and an ETag header.
  2. Save this JSON response in a way that will persist even after the app is restarted by the user.
  3. Whenever this HTTP request is repeated, send an If-None-Match header.
    1. When you get a "Not Modified" response, use the version in the persisted cache.
    2. When you get a "Successful" response (meaning the response has changed), invalidate the persisted cache, save the new response.

Does React Native have a component that does these things out of the box? If not, what is the most common way people use to handle this?

Upvotes: 12

Views: 6306

Answers (4)

Esben von Buchwald
Esben von Buchwald

Reputation: 3122

In my case I had to put a Cache-control: max-age=0 header in my request, in order to make iOS actually validate the stored value with the server, using the Etag, otherwise it would just use the cache value without requesting the server, for a while after the first response.

Also keep in mind that Android will not honor the ETag if 'no-cache' is set, even though the standard says that 'no-cache' means that the client must validate the cached value with the server befefore using it. But android always makes a "clean" request if 'no-cache' is set.

Upvotes: 0

Jack Vo
Jack Vo

Reputation: 289

The fetch() API of React native is following the http caching spec and it provides this feature. When you hit a 304 a 200 old response will be found in the cache and be reused.

Details:

https://github.com/heroku/react-refetch/issues/142

As answered at: https://stackoverflow.com/a/51905151

React Native’s fetch API bridges to NSURLSession on iOS and okhttp3 on Android. Both of these libraries strictly follow the HTTP caching spec. The caching behavior will depend primarily on the Cache-Control and Expires headers in the HTTP response. Each of these libraries have their own configuration you can adjust, for example to control the cache size or to disable caching.

And this: How to use NSURLSession to determine if resource has changed?

The caching provided by NSURLSession via NSURLCache is transparent, meaning when you request a previously cached resource NSURLSession will call the completion handlers/delegates as if a 200 response occurred.

If the cached response has expired then NSURLSession will send a new request to the origin server, but will include the If-Modified-Since and If-None-Match headers using the Last-Modified and Etag entity headers in the cached (though expired) result; this behavior is built in, you don't have to do anything besides enable caching. If the origin server returns a 304 (Not Modified), then NSURLSession will transform this to a 200 response the application (making it look like you fetched a new copy of the resource, even though it was still served from the cache).

Upvotes: 4

Momen Zalabany
Momen Zalabany

Reputation: 9007

okay, this is my current solution, not production tested yet. would love your feed back googlers.

i use Axios, but if you dont you still implement this around what ever wrapper you have around fetch -unless u use native fetch !-

import api from 'your api wrapper.js'
api.etags = new Set;
api.cache = new Set;
api.addRequestTransform(request => {
  const etag = api.etags.get(request.url);
   if (etag) {
     request.headers['HTTP_IF_NONE_MATCH'] = etag;
   }
})

// or whatever you use to wrap ur HTT
api.addResponseTransform(response =>{ 
if (
    response.status === 304 &&
    response.headers &&
    response.headers.etag &&
    api.cache.has(response.headers.etag)
  ) {
    console.log('%cOVERRIDING 304', 'color:red;font-size:22px;');
    response.status = 200;
    response.data = api.cache.get(response.headers.etag);
  } else if (response.ok && response.headers && response.headers.etag) {
    api.cache.set(response.headers.etag, response.data);
    api.etags.set(response.config.url, response.headers.etag);
  }
});

what we are doing here is saving response result into api.cache, and saving the etags into api.etag, then we send etag with request every time.

we can upgrade this to also remember the correct status code, or save etags to disk, duno. what do you think :) ?

Upvotes: 1

vbullinger
vbullinger

Reputation: 4256

Oof. Been over a year. I assume you know this is a resounding "no," right? You'll have to parse the response headers to grab the ETag and store that on the device (you're not using the browser) and then add the header to the subsequent requests after retrieving it from your storage mechanism of choice.

I just found this because I was looking to see if anybody had done this in React, let alone React Native, and I'm not seeing anything.

Whelp, time to roll up my sleeves and invent this thing...

Upvotes: 1

Related Questions