Redux state tree structure: "same type of data with different format / amounts of detail"

I've done Dan Abramov's intro series on EggHead, and am working on a real world app. The domain is complex, so I'll run with a classic "blogs" example.

Let's say, we have an "index / list" page, where all we need to show are the blog post's title and blurb. So we have an API endpoint that returns that, and we store it in our state tree under blogs.byId.

Then, when you click through on a blog post, we actually need a bunch more info - e.g. the full blog post, and also tags and categories. Let's call this "blogs with metadata".

Stretching the example, there might be another completely separate page where I want to display a list of blog posts with the most recent 3 comments. Let's call this "blogs with comments".

My question is, how should my state tree treat these separate examples where I'm storing the same "thing" but in different "formats"? My initial hunch would be to treat them as completely separate data types, so my state tree would have eg: blogs.byId, blogsWithMetadata.byId and blogsWithComments.byId.

And then, even if every single blog post is cached in the blogs.byId section, the minute we need to view a blog post, the app completely ignores that warm blogs.byId cache, and looks only at blogsWithMetadata.byId - so we'd essentially be building up 3 separate caches of blog data, each with different amounts of info, and treating it as though they are as unrelated to each other as "blogs" and a completely unrelated table like "widgets" would be.

Is this correct? Or is there a better way?

The app currently rams them all under the same node, without distinction based on "format" and it's causing a world of pain.

Answers

There are probably many ways you could choose to do this. One of it is to use normalizr to structure your data.

Your blog post could have a data structure returned by the API like this:

{
  "id": "123",
  "author": {
    "id": "1",
    "name": "Paul"
  },
  "title": "My awesome blog post",
  "comments": [{
    "id": "324",
    "commenter": {
      "id": "2",
      "name": "Nicole"
    }
  }],
  "tags": [{
    "id": "1",
    "value": "awesome"
  }, {
    "id": "2",
    "value": "journal"
  }],
  "categories": [{
    "id": "1",
    "value": "personal"
  }, {
    "id": "2",
    "value": "life"
  }]
}

which after normalizing, will look something like this:

{
  entities: {
    "post": { 
      "123": { 
        id: "123",
        author: "1",
        title: "My awesome blog post",
        comments: ["324"],
        tags: ["1", "2"],
        categories: ["1", "2"],
      }
    },
    "users": {
      "1": { "id": "1", "name": "Paul" },
      "2": { "id": "2", "name": "Nicole" }
    },
    "comments": {
      "324": { id: "324", "commenter": "2" }
    }
    "tags": {
      "1": { id: "1", "value": "awesome" },
      "2": { id: "2", "value": "journal" },
    }
    "categories": {
      "1": { id: "1", "value": "personal" },
      "2": { id: "2", "value": "life" },
    }
  }
}

Subsequently, you could have a state for each page if you needed to:

{
  entities: {...},
  ui: {
    blogs: {
      posts: [1, 2],
      hasComments: false,
      // Displaying the blogs with or without comments
      // could simply just be a boolean flag in state.
    },
  }
}

using reselect, you then create the selectors to pass the posts you want as props to the page Components.

Posted on by Calvin

Relevant tags