How Do We Sync Reactions Across Hashnode

How Do We Sync Reactions Across Hashnode

ยท

3 min read

Hashnode is a blogging platform with many feeds. We have relevant feed, featured feed, recent feed, bookmark feed, and reading history feed to list some.
Whenever an article receives any engagement like reactions, comments, or bookmarks, it must be reflected across all the feeds simultaneously. Let's see how we manage the app state to sync every interaction across Hashnode.

Old Approach And Issues With It

We kept things very simple when we started building the mobile app. Every feed gets stored in a separate array.
Since feeds can be scrolled infinitely, the above approach was easier to manage. We could simply add more items to the array whenever you scroll to the end of the screen.

Things like pull to refresh, filtering, etc were also straightforward. Here's how it would look in practice.

// example of how the state would look
const state = {
  relevant: [], // array contains article objects
  featured: [],
  recent: [],
  readingHistory: [],
  bookmarks: [],
  // etc
};

This approach had a major flaw. Since every feed is independent and maintains an array, any engagement, for example, a reaction has to be updated in all the arrays.

An article in one feed might not be present in another feed so we have to add a check for that as well.

Every update like adding, updating, or removing comments, replies, bookmarks, and reactions has to be added to all the feeds so that they stay consistent. This is pretty difficult to manage and requires a lot of code as well. At this time we asked ourselves, can we do better?

The Better Approach

The core idea was to update every article once and derive every state from a single source of truth. We thought of storing every article once in an object instead of an array.

Objects can map article data by their IDs. Any actions like finding an article, updating it, removing it, or adding it are O(1). Since we are mapping by IDs, the articles we are storing are unique.

const state = {
  postsById: {
    // contains postId and post data as key-value pair
  },
};

We have successfully avoided duplication. Now we just have to update the feed array to derive the state from postsById. We can do this by storing the IDs of posts inside the feed array.

const state = {
  postsById: {
    // contains postId and post data as key value pair
  },
  relevant: [], // store only IDs as string
  featured: [],
  recent: [],
  readingHistory: [],
  bookmarks: [],
};

Now instead of directly using the array inside the component, we have to derive it from the above state.

// for relevant feed
const relevantPosts = state.relevant.map(id => state.postsById[id]);
// similarly for bookmark feed
const bookmarkedPosts = state.bookmarks.map(id => state.postsById[id]);

We are making sure every feed is dependent on postsById only. Now every interaction will update the data inside postsById once and it will reflect everywhere irrespective of which screen is in focus.

Although this operation is O(n), we are loading a small finite number of posts at any given time so this approach should not result in any performance issues.

Testing Benefits

We use React Native Testing Library heavily at Hashnode. Having postsById as the only source of truth makes testing seamless.
We can get the id of the article we want to test from the feed array and assert the value inside postsById. This reduces the number of tests as well since we don't have to check for changes in every feed.

This is how we make sure your reactions, comments, and bookmarks are always synced between multiple feeds. Let us know how you feel about this approach. We would love to from you if you think this can be improved further.

Download the Hashnode app now if you haven't already. We have an excited roadmap ahead and app will receive some really cool updates soon.