# Generalising models via helpers

You may identify repeated patterns within your store implementation. It is possible to generalise these via helpers.

For example, say you had the following:

const store = createStore({
  products: {
    data: {},
    ids: computed(
      [state => state.data],
      (resolvedState) => {
        const [data] = resolvedState;
        return Object.keys(state.data)
      }
    ),
    fetched: action((state, products) => {
      products.forEach(product => {
        state.data[product.id] = product;
      });
    }),
    fetch: thunk(async (actions) => {
      const data = await fetchProducts();
      actions.fetched(data);
    })
  },
  users: {
    data: {},
    ids: computed(
      [state => state.data],
      (resolvedState) => {
        const [data] = resolvedState;
        return Object.keys(state.data)
      }
    ),
    fetched: action((state, users) => {
      users.forEach(user => {
        state.data[user.id] = user;
      });
    }),
    fetch: thunk(async (dispatch) => {
      const data = await fetchUsers();
      actions.fetched(data);
    })
  }
})

You will note a distinct pattern between the products and users. You could create a generic helper like so:

const dataModel = (endpoint) => ({
  data: {},
  ids: computed(
    [state => state.data],
    (resolvedState) => {
      const [data] = resolvedState;
      return Object.keys(state.data)
    }
  ),
  fetched: action((state, items) => {
    items.forEach(item => {
      state.data[item.id] = item;
    });
  }),
  fetch: thunk(async (actions, payload) => {
    const data = await endpoint();
    actions.fetched(data);
  })
})

You can then refactor the previous example to utilise this helper like so:

const store = createStore({
  products: {
    ...dataModel(fetchProducts)
    // attach other state/actions/etc as you like
  },
  users: {
    ...dataModel(fetchUsers)
  }
})

This produces an implementation that is like for like in terms of functionality but far less verbose.

# TypeScript version

We can utilise TypeScript to create model helpers too. Here is the same example adapted for TypeScript.

export interface ObjectWithId {
  id: string;
}

export interface DataModel<DataItem extends ObjectWithId> {
  data: { [key: string]: DataItem };
  ids: Select<DataModel<DataItem>, string[]>;
  fetch: Thunk<DataModel<DataItem>>;
  fetched: Action<DataModel<DataItem>, DataItem[]>;
}

export const dataModel = <Items extends ObjectWithId>(
  endpoint: () => Promise<Items[]>
): DataModel<Items> => ({
  data: {},
  ids: select(state => Object.keys(state.data)),
  fetched: (state, items) => {
    state.data = items;
  },
  fetch: thunk(async (actions, payload) => {
    const data = await endpoint();
    actions.fetched(data);
  })
});