Gaining Visibility Into Your State Changes In Redux

Two of the most important skills to have in a programming are to be able to get quick visibility into what is actually going on in your program at a given point in time, and second, to be able to quickly isolate any problems that you run into. The former of these actually greatly facilitates the latter.

While working on a strange and infuriating issue recently with a React Native/Redux application that I was working on, I found myself in a situation where I felt blind as to what was actually going on in my application (and why it was failing).

You see, mapStateToProps was never being called, yet my reducer was definitely being called and I was definitely returning a new state object from my reducer. I had confirmed this with console.logs. Additionally, I had already confirmed that mapStateToProps was definitely not being called at all (again, using console.logs).

After quintuple checking that everything was hooked up properly, and that my reducer was indeed returning new state (and not making the mistake of altering existing state in the reducer), I decided that whatever the error was, it was happening in between the time that the reducer returned the new state and the time that mapStateToProps was being called. In other words, it seemed like it wasn’t something in my code.

Since it is a rather rare occurrence that the bug is in the framework code and not my own code, I wanted some additional visibility into what was going on. I needed to first confirm that Redux knew the state had been updated and that it should therefore be calling mapStateToProps on all of my connected components.

It turns out, Redux has a perfect little tool for this. It is called state.subscribe().

You can add a few lines that look like this:

store = createStore( ... );
store.subscribe(() => {
  console.log("Store state changed");
  console.log(store.getState());
});

wherever you have defined your store (using createStore). Using this, you get some log output any time the store’s state is changed. Additionally, you can see exactly what the new state is.

I should also note that much of this visibility can also be achieved with the Redux DevTools extension (which also comes with quite a few nice additional features), but I wanted to be able to play with these events, and in so doing I gained more of an understanding of what is really going on under the hood in Redux.

Gaining Visibility Into Your State Changes In Redux

Retrieving Files As Blob in React Native (and Expo)

tl;dr
Sometimes you may need to create a valid blob file from a remote file or a local file (for example, for uploading a screenshot to firebase storage). If the Blob object returned by the fetch API (e.g. blob = await fetch(`file://${local_uri}`).blob()) is returning an empty type and/or 0 bytes, then upgrading to React Native 0.59.x+ (expo sdk 33+) should fix the issue. If upgrading is not a possibility, there are other options such as creating a (hacky) blob utility function, or passing around base64 encoded data (see below).

I want to note that if you simply want to use an Image component to display the local file in the UI, you can specify the local location of the file as the source.

Note also that I am not referring to the situation where your file can be packaged with your app ahead of time or where it can be directly loaded into the UI from the internet. If your file is being downloaded from the internet it can usually be loaded directly via URL. If it is something that you can package with your app, the best practice is to simply use an import or require to include the file at compile time.

However, for more complicated use cases, such as uploading the file, it can get quite a bit trickier.

Uploading files that are dynamically created in-app (such as a screenshot), for example, can be tricky. Also, fetching any file prior to React Native 0.59.x with the fetch API has issues associated with it.

If you are not using Expo, it is worth looking into rn-fetch-blob. Unfortunately, since I was using Expo, I could not use rn-fetch-blob without detaching and using ExpoKit (something I would like to avoid for a task that should be relatively simple).

In my case, uploading a dynamically created tempfile to firebase took quite a bit of effort. I would like to share a couple of solutions I came up with.

The main problem for me came down to an issue with React Native’s custom whatwg-fetch polyfill in versions prior to 0.59. RN’s custom polyfill specifically does not use a ‘blob‘ response type (blob support was added to React Native and Expo in early 2018). Therefore, fetching files with it (vs, for example, fetching JSON) caused an issue where the resulting blob would have 0 bytes and an empty string as the ‘type’. Let me demonstrate with an example using a basic expo app.

We will start by attempting to download a jpg from the internet as a blob, and then we will output the fetch Response object and the Blob object that results from calling blob() on it (note that fetch’s Response.blob() returns a promise, so we have to await or resolve it to get the actual object):

App.js:

import React from 'react';
import { Text, View } from 'react-native';

const getFile = async () => {
  const img_url = "https://picsum.photos/200/300.jpg";
  let result = await fetch(img_url);
  console.log(result);
  console.log(await result.blob());
  return result;
}

export default function App() {
  getFile();
  return (
    <View>
      <Text>Open up App.js to start working on your app!</Text>
    </View>
  );
}

And the data looks like this:

Notice that the size of the Blob object is 0 and the type is an empty string.

We can try something similar by fetching JSON and calling json() on the Response object, but this time we get the expected JSON output:

const getFile = async () => {
  const json_url = "https://jsonplaceholder.typicode.com/todos/1";
  let result = await fetch(json_url);
  console.log(result);
  console.log(await result.json());
  return result;
}

And we get exactly what we expected – a javascript object with the correct JSON values.

Additionally, we can upgrade to expo SDK 33, which uses React Native 0.59 and we also get what we expect:

App.js (Expo SDK 33 / React Native 0.59):

import React from 'react';
import { Text, View } from 'react-native';

const getFile = async () => {
  const img_url = "https://picsum.photos/200/300.jpg";
  let result = await fetch(img_url);
  console.log(result);
  console.log(await result.blob());
  return result;
}

export default function App() {
  getFile();
  return (
    <View>
      <Text>Open up App.js to start working on your app!</Text>
    </View>
  );
}

Success! We have successfully fetched a file as a valid JS Blob object (note the correct type/size info). Now we can use it in the large number of javascript File API’s that accept Blobs.

So what is happening here?

For very specific reasons, prior to version 0.59 React Native had a custom whatwg-fetch polyfill implementation that specifically does NOT return a blob responseType by default.

However for these reasons that custom polyfill has become unnecessary. In version 0.59 it was altered to return a responseType of ‘blob’ by default (which is what enables the Response.blob() function to work correctly). And then after 0.59 the custom polyfill was removed altogether as it was now redundant.

The simple solution is to upgrade to React Native 0.59 or higher (or Expo SDK 33, the highest at the time of this writing).

After upgrading to RN 0.59 my resulting Blob objects have a type of ‘image/jpeg’ and a correct size value. More importantly, now they properly upload to Firebase storage as valid images without any problems.

But what if upgrading is not an option?

If you are downloading the file from the internet you can write a urlToBlob utility that uses XMLHttpRequest to grab the file as a blob:

function urlToBlob(url) {
  return new Promise((resolve, reject) => {
      var xhr = new XMLHttpRequest();
      xhr.onerror = reject;
      xhr.onreadystatechange = () => {
          if (xhr.readyState === 4) {
              resolve(xhr.response);
          }
      };
      xhr.open('GET', url);
      xhr.responseType = 'blob'; // convert type
      xhr.send();
  })
}

If you are creating a screenshot using takeSnapshotAsync (as I was) you also have the option to set the result as a ‘data-uri’ to get a base64 encoded data version of the file. This base64 data can then be turned into a blob or, in the case of Firebase, uploaded directly as a data_url encoded string.

I found that the base64 string created by takeSnapshotAsync was exceptionally large compared to the actual image, so I chose to utilize an actual image, storing it in the local cache, grabbing it with fetch and then uploading it to firebase from there.

Retrieving Files As Blob in React Native (and Expo)

Displaying A Local Image File In The UI on Android (React Native)

<Image
  style={{ width: image_width, height: image_height }
  source={{ uri: `file://${local_file_uri}` }}
/>

The width and the height values appear to be required. Also, some tutorials will say to use file:/// (three slashes). That worked for me in IOS, but not on Android.

Displaying A Local Image File In The UI on Android (React Native)