goatslacker / alt

Isomorphic flux implementation
http://alt.js.org/
3.45k stars 322 forks source link

Nested / complex data structure #435

Open srph opened 9 years ago

srph commented 9 years ago

Not specifically to Alt, though. I am not sure how to structure my stores with this certain situation.


Let's say, we have 3 resources: Member, Group, and Conversation.

Member belongsToMany Conversation; and vice-versa. (x:x) Member belongsTo Group (x:1)

Group and Conversation has no direct relationship. This is because Group is only being used to literally contain a Member.

* Note that the API route (api/groups/ -- which is expected to return all groups also includes its contacts) . It returns something like this:

{
  "id": 1,
  // ....
  "members": [
    {
      "id": 4,
      // ...
    }
  ]
}

This is the UI:

screenshot 2015-08-03 13 56 36

This is for creating a Message. A message requires contacts (ids) and a few fields. The Group checkbox is simply used to select all contacts under it. It does not have anything to do with the Message itself.


Currently, I just have a GroupStore and SelectedContactStore. However, I feel that the current setup is a bit tedious, and very prone to errors. I am not sure if this is due to Immutable.

Here's an idea of the code:

// SelectedMemberStore
import { createStore } from 'alt/utils/decorators';
import Immutable from 'immutable';
import immutable from 'alt/utils/ImmutableUtil';

import alt from '../alt';
import MemberActions from '../actions/MemberActions';
import GroupStore from '../actions/GroupStore';

@createStore(alt)
@immutable
class SelectedMemberstore {
  state = {
    selected: Immutable.List()
  };

  constructor() {
    this.bindListeners({
      // onFetchAll: onFetchAll,
      onSelectMember: MemberActions.SELECT_MEMBER,
      onSelectGroup: MemberActions.SELECT_GROUP
    });

    this.exportPublicMethods({
      getIndexById: ::this.getIndexById,
      isMemberSelected: ::this.isMemberSelected,
      isGroupSelected: ::this.isGroupSelected
    });
  }

  isGroupSelected(groupId) {
    const group = GroupStore.getById(groupId);
    const { selected } = this.state;
    const members = group.get('members');

    return !members.size ? false : members
      .filter(member => selected.indexOf(member.get('id')) !== -1)
        .size === members.size;
  }

  isMemberSelected(id) {
     return this.getIndexById(id) !== -1;
  }

  getIndexById(id) {
    return this.state.selected.indexOf(id);
  }

  onSelectMember(id) {
    this.setState({
      selected: this.state.selected.push(id)
    });
  }

  onSelectGroup(groupId) {
    const group = GroupStore.getById(groupId);
    const members = group.get('members');
    const { selected } = this.state;

    this.setState({
      selected: !this.isGroupSelected(group) ? selected.concat(
        members.filter(member => (
          selected.indexOf(member.get('id')) === -1
        )).map(member => member.get('id'))
      ) : selected.filter(id => (
        members.map(member => member.get('id')).indexOf(id) === -1
      ))
    });
  }
}

export default SelectedMemberstore;

Any better way to do this?

joshhornby commented 9 years ago

Have you seen https://github.com/gaearon/normalizr?

goatslacker commented 9 years ago

Yeah you probably want something like normalizr