Basics

HrState is the internal data shape for your high-redux reducers. Normally you don’t interact with it directly, but instead go through the the HrQuery interface. Documenting it here might help you understand how high-redux works.

We’ll start with the formal type definition, and then go into examples. Note that this describes an interface (a defined type of JS primitives), not a class. It’s typically wrapped in an HrStateWrapper class instance to allow updates, or an HrQuery class instance to query it for data.

Type

If you’re unfamiliar with type annotation syntax, don’t worry about it. The examples should give you a better idea of how it looks. The full structure of the state:

export type HrState = {
  isHrState: true,
  byId: HrStateById,
  lists: HrStateList,
  kv: HrStateKv,
  rollbackOps: RollbackOps,
}
type HrStateById = { [type: string]: { [id: string]: HrStateDesc<any> } };
type HrStateList = { [type: string]: HrStateDesc<Array<any>> };
type HrStateKv = { [type: string]: { [id: string]: HrStateDesc<any> } };

You may notice the HrStateDesc types. They’re a consistent interface to describing a record in state. While in classic redux you often store data without any metadata, having this available by default ensures you can track things like loading and error states as your code evolves.

There’s also the ‘etc’ property, where you can put any custom data you want.

export type HrStateDesc<T> = {
  loading: boolean,
  hasError: boolean,
  error: ?any,
  value: T,
  loadingStartTime: number,
  loadingCompleteTime: number,
  etc: Object,
}

Examples

First, let’s look at what a blank state looks like. We simply return the HrStateWrapper without making any changes.

const { reducer } = hr.makeHr({
  name: 'example',
  actions: {
    TEST: s => s,
  },
});

const state = runReducer(reducer, { type: 'TEST' });

print(state);
{
  "isHrState": true,
  "byId": {},
  "lists": {},
  "kv": {},
  "rollbackOps": {}
}

Now it gets a little more interesting. We’re setting an item by id using the default key, which is the same as s.key('[[default]]').id(....

const { reducer } = hr.makeHr({
  name: 'example',
  actions: {
    SET_ID: (s, payload) => (
      s.id(payload.id).set(payload.data)
    ),
  },
});

const state = runReducer(reducer, {
  type: 'SET_ID',
  payload: { id: '123', data: { name: 'John' } },
});

print(state);
{
  "isHrState": true,
  "byId": {
    "[[default]]": {
      "123": {
        "loading": false,
        "hasError": false,
        "error": null,
        "value": {
          "name": "John"
        },
        "loadingStartTime": 0,
        "loadingCompleteTime": 0,
        "etc": {}
      }
    }
  },
  "lists": {},
  "kv": {},
  "rollbackOps": {}
}

We could set some metadata with object. In this case, we set the loading state, and it also captures loadingStartTime for us.

const { reducer } = hr.makeHr({
  name: 'example',
  actions: {
    FETCH_USER_START: (s, payload) => (
      s.id(payload.id).setLoading()
    ),
  },
});

const state = runReducer(reducer, {
  type: 'FETCH_USER_START',
  payload: { id: '123' },
});

print(state);
{
  "isHrState": true,
  "byId": {
    "[[default]]": {
      "123": {
        "loading": true,
        "hasError": false,
        "error": null,
        "value": null,
        "loadingStartTime": 1516012256897,
        "loadingCompleteTime": 0,
        "etc": {}
      }
    }
  },
  "lists": {},
  "kv": {},
  "rollbackOps": {}
}

The key/value store works identically to the id store, but list is somewhat different. Instead of each item having its own descriptor, the entire list has one descriptor.

const { reducer } = hr.makeHr({
  name: 'example',
  actions: {
    FETCH_LETTERS_SUCCESS: (s, payload) => (
      s.list().set(payload)
    ),
  },
});

const state = runReducer(reducer, {
  type: 'FETCH_LETTERS_SUCCESS',
  payload: ['a', 'b', 'c'],
});

print(state);
{
  "isHrState": true,
  "byId": {},
  "lists": {
    "[[default]]": {
      "loading": false,
      "hasError": false,
      "error": null,
      "value": [
        "a",
        "b",
        "c"
      ],
      "loadingStartTime": 0,
      "loadingCompleteTime": 0,
      "etc": {}
    }
  },
  "kv": {},
  "rollbackOps": {}
}

For all of the types, you can also set a custom key.

const { reducer } = hr.makeHr({
  name: 'example',
  actions: {
    FETCH_LETTERS_SUCCESS: (s, payload) => (
      s.key('my-key').list().set(payload)
    ),
  },
});

const state = runReducer(reducer, {
  type: 'FETCH_LETTERS_SUCCESS',
  payload: ['a', 'b', 'c'],
});

print(state);
{
  "isHrState": true,
  "byId": {},
  "lists": {
    "my-key": {
      "loading": false,
      "hasError": false,
      "error": null,
      "value": [
        "a",
        "b",
        "c"
      ],
      "loadingStartTime": 0,
      "loadingCompleteTime": 0,
      "etc": {}
    }
  },
  "kv": {},
  "rollbackOps": {}
}

We could go into many more examples, but this should give you the general idea.

Summary

The HrState type is a future-proof interface to managing redux state. It makes few assumptions about how you want to use it.