[SOLVED] Meteor Grapher Unlimited Link Loopback

I have 3 collections:

  1. Menus
  2. Sections
  3. Items

Menus can contain sections and items. Sections can contain sections and items.

How do I query this using Grapher’s createQuery?

The documentation mentions link loopback (which is what I think I need) here.

But it manually nests these loopbacks so essentially puts a limit on it:

Meteor.users.createQuery({
    $filters: {_id: userId},
    friends: {
        nickname: 1,
        friends: {
            nickname: 1,
            friends: {
                nickname: 1,
            }
        }
    } 
});

It only nests ‘friends’ twice. This would set a limit on the nesting / loopback wouldn’t it?

Is there a way to set this as unlimited? Or do I need to decide on a limit and stick to that?

I’ve managed to resolve this using nested lists with nested withQuery reactivity, rather than defining it in the create query function.

I’m not sure if there are any performance implications for this currently but it does work so I thought I would share this in case anyone else has this issue.

I have essentially got a top level (Menu) list component:

const MenuList = ({ data, isLoading, error }) => {
  if (error) {
    console.log(error);
  }

  return (
    <>
      {
        isLoading ? 'Loading...' : (  
            {
              data.map(menu => {
                return <MenuListItem key={menu._id} menu={menu} />
              })
            }
        )
      }
    </>
  );
};

export default withQuery(({userId}) => {
  return menusByUser.clone({userId})
}, {reactive: true})(MenuList)

This renders a list of MenuListItem components. This item component contains nested list components for Sections and Items (SectionList & ItemList):

MenuListItem:

export const MenuListItem = ({menu}) => {

  return (
    <>
          <SectionList userId={Meteor.userId()} parentId={menu._id} />

          <ItemList userId={Meteor.userId()} parentId={menu._id} />
    </>
  );
};

The SectionList and ItemList component work very similarly (almost identically) to the MenuList:

const SectionList = ({ data, isLoading, error }) => {
  if (error) {
    console.log(error);
  }

  return (
    <>
      {
        isLoading ? 'Loading...' : (
          <>
              {
                data.map(section => {
                  return <SectionListItem key={section._id} section={section} />
                })
              }
          </>
        )
      }
    </>
  );
};

export default withQuery(({userId, parentId}) => {
  return sectionsByParent.clone({userId, parentId})
}, {reactive: true})(SectionList)
const ItemList = ({ data, isLoading, error }) => {
  if (error) {
    console.log(error);
  }

  return (
    <>
      {
        isLoading ? 'Loading...' : (  
          <>
              {
                data.map(item => {
                  return <ItemListItem key={item._id} item={item} />
                })
              }
          </>
        )
      }
    </>
  );
};

export default withQuery(({userId, parentId}) => {
  return itemsByParent.clone({userId, parentId})
}, {reactive: true})(ItemList)

The SectionList component calls SectionListItem’s which can in-turn also call SectionList’s (the nesting part of this issue):

export const SectionListItem = ({section}) => {

  return (
    <>           
          <SectionList userId={Meteor.userId()} parentId={section._id} />

          <ItemList userId={Meteor.userId()} parentId={section._id} />
    </>
  );
};

These are my queries:

menusByUser (query used in MenuList withQuery):

const query = createQuery('menusByUser', {
  menus: {
    $filter({filters, params}) {
      filters.userId = params.userId
    },
    name: 1
  }
});

sectionsByParent (query used in SectionList withQuery):

const query = createQuery('sectionsByParent', {
  sections: {
    $filter({filters, params}) {
      filters.userId = params.userId,
      filters.parentId = params.parentId
    },
    name: 1,
    sections: {
      name: 1
    },
    items: {
      name: 1
    }
  }
});

itemsByParent (query used in ItemsList withQuery):

const query = createQuery('itemsByParent', {
  items: {
    $filter({filters, params}) {
      filters.userId = params.userId,
      filters.parentId = params.parentId
    },
    name: 1
  }
});

As you can see, there is no nesting within the queries - all nesting is done within the React front-end components. It runs a withQuery render on each nested SectionList to reactively update nested sections without setting a limit.

The ‘parentId’ field is used on sections and items as a parent could be a menu or a section.