🚂 A Cloud Traveler’s Side Quest: Debugging Firestore events
Tags: googlecloud, firebase, redux, storytelling
Published: 2024-03-01 07:05:19

---
title: 🚂 A Cloud Traveler’s Side Quest: Debugging Firestore events
published: true
description: Follow Cloud Traveler as they debug Redux + Cloud Firestore.
tags: #googlecloud #firebase #redux #storytelling
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dvjr71qpcgxzn4536wje.png
---

| **Series:** 🚂 [Journey to Cloud City!](https://dev.to/pattishin/series-journey-to-cloud-city-4l2n) |
--------------------
| **Episode:** 0 |
| **Characters:** Cloud Traveler, a new engineer learning about Cloud |
| **Tools:** [Redux](https://redux.js.org/tutorials), [Cloud Firestore](https://cloud.google.com/firestore), [Google Cloud Platform](https://cloud.google.com) |


Welcome to episode 0. We’re engineers, we start counting from 0 not 1.
I wanted to experiment following a character, Cloud Traveler, getting into the weeds and debugging these everyday sort of issues using known cloud and frontend tools.

**Issue:** When a Firestore `onSnapshot` function is declared within a Redux action (`createActionThunk()`), connected Redux reducers do not “seem” to be getting called when Firestore collection emits a change event.

**A solution:** Redux actions should always return a value per the one-way data flow model and any listener function should be initialized once in an application. Create a new getter action that will retrieve the new state of the collection rather than relying on the change values given in the `onSnapshot` callback function.

Please note, this is a nice hack for smaller projects. If you want to build event-driven architecture with Cloud Firestore events, recommend that you take a look at [Google Cloud’s EventArc](https://cloud.google.com/firestore/docs/eventarc).

-----------------------------------------------------

Cloud Traveler is annoyed.

Getting their first web development project to track how much sunlight their plants are receiving was relatively frictionless leveraging [Firebase](www.firebase.com) and [Cloud Firestore](https://cloud.google.com/firestore). However, getting reliable and deterministic event data from this new collection has been frustrating. The [Redux](https://redux.js.org/tutorials) action, `pingSunlightLevel` is meant to send relevant change data information up to the reducer, `plantSlice` to the main store data. However, it's not working as expected. 

```js

// Actions
export const pingSunlightLevel = createAsyncThunk ('pingSunlightLevel', async () => {
  const ref = firebaseInstance.db.collection('plant_sunlight');
  await ref.onSnapshot((snapshot) => {
    snapshot.docChanges().forEach((change) => ({ change });
  });
)};

// Reducer
const plantSlice = createSlice({ 
  name: 'plants',          
 . . .
  extraReducers: (builder) => {
    builder
      .addCase(pingSunlightLevel.fulfilled, (state, action) => {
        state.plantLightLevel = action?.payload?.change;
        return state;
      })
  . . .
  },
});

```

*"Time to change my perspective"* sighs Cloud Traveler, as they put on their Sherlock Holmes cap. *“Let me think about what I know of the [Redux one way data flow](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#redux-application-data-flow) and leverage some debugging statements in that snapshot callback function."*

The snapshot is receiving the expected Firestore events when the collection is updated. But, for some reason the reducer isn't responding.

Lo and behold, they find that the snapshot was receiving the expected [Firestore events](https://firebase.google.com/docs/firestore/query-data/listen) when the `plant_sunlight` collection was updated. Bad news was that the culprit seems to be that the reducer was somehow not responding. 

```js

// Actions
export const pingSunlightLevel = createAsyncThunk ('pingSunlightLevel', async () => {
  const ref = firebaseInstance.db.collection('plant_sunlight');
  await ref.onSnapshot((snapshot) => {
    snapshot.docChanges().forEach((change) => {
      console.log(change);  // Getting changes
      debugger
      return { change };
    }); 
  });
)};

// Reducer
const plantSlice = createSlice({ 
  name: 'plants',          
 . . .
  extraReducers: (builder) => {
    builder.addCase(pingSunlightLevel.fulfilled, (state, action) => {
     console.log(action.payload); // Unresponsive 
     state.plantLightLevel = action?.payload?.change;
     return state;
  });
  . . .
  },
});
```

Cloud Traveler furrows their brow. 

*“I need to figure out why the reducer isn't responding. I'll go through the possibilities one at a time to narrow down the issue.*

*Was the connected UI button triggering the correct action? 
✅ Check*

*Was the reducer connected to the main Redux store correctly? 
✅ Check.*

*Was the reducer referencing the right Redux action function?
✅ Sure looks like it.*

*What happens if I comment out that snapshot function, would the reducer respond to the action if I pass return just a dummy value?
🤨 Oh ..."*

The reducer, at this point, did get called. 
The two pieces Cloud Traveler had investigated, 
the snapshot function and the Redux flow, seemed to be misaligned. 

Cloud Traveler’s `pingSunlightLevel` Redux action was, in fact, not returning a value as it should per the one-way data flow model when the snapshot function was declared within it. Instead, it was creating additional instances of the listener inside their application on every action call!

Seeing a path forward, Cloud Traveler refactors out the snapshot function `pingSunlightLevel` to only be declared once and updates the action function, now `getPlantSunlightLevel`, to just retrieve the current state of `plant_sunlight` collection.

THE FINAL VIEW:
```js

// Actions
// Newly created action that gets called by pingSunlightLevel
export const getPlantSunlightLevel = createAsyncThunk ('getPlantSunlightLevel', async () => {
  const ref = firebaseInstance.db.collection('plant_sunlight');
  const currentState = await ref.get().then((querySnapshot) => {
    let list = [];
    querySnapshot.docs.forEach(doc => list.push(doc.data()));
    return list;
  });
  return { currentState };
});

// Previous action, now just a Firestore listener
export const pingSunlightLevel = async (dispatch) => {
  const ref = firebaseInstance.db.collection("plant_sunlight");
  await ref.onSnapshot((snapshot) => {
    snapshot.docChanges().forEach(() => dispatch?.(getPlantSunlightLevel()));
  });
};

// Reducer
const plantSlice = createSlice({ 
  name: 'plants',          
 . . .
  extraReducers: (builder) => {
    builder
      .addCase(pingSunlightLevel.fulfilled, (state, action) => {
        state.plantLightLevel = action?.payload?.currentState;
        return state;
      })
  . . .
  },
});

```

Finally at ease, our hero, Cloud Traveler can now observe real-time sunlight levels in their new plant web application.

**FIN**