Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fragment composition with Apollo client: convention and boilerplate

In an Apollo app (but also GraphQL/Relay), one can choose to colocate data requirements to components, or eventually assemble big GraphQL queries himself. We have choosen to colocate data requirements to components because we expect better maintainability on the long terme, as you don't need to look at the whole component tree or your page to see all the data requirements, and can add new requirements locally.

I'd like to know better how to compose GraphQL fragments with Apollo client. I know how to do it, but I'd like to know how can I do it better.

Currently, composing my fragments involve quite a bunch of boilerplate, particularly when I have components that just pass down the properties untouched.

Fragment declaration convention?

First, let's take a simple component:

export const User = ({
  user: {
    firstName,
    lastName,
    job,
    email,
    pictureUrl,
    color
  },
  ...props
}) => (
  <UserWrapper {...props}>
    <UserAvatarWrapper>
      <Avatar
        firstName={firstName}
        lastName={lastName}
        color={color}
        src={pictureUrl}
      />
    </UserAvatarWrapper>
    <UserContentWrapper>
      {(firstName || lastName) &&
        <UserName>
          {firstName}
          {" "}
          {lastName}
          {" "}
          {email && <UserEmailInline>{email}</UserEmailInline>}
        </UserName>}
      {job && <UserJob>{job}</UserJob>}
    </UserContentWrapper>
  </UserWrapper>
);
User.fragments = {
  user: gql`
      fragment User on User {
          id
          firstName
          lastName
          pictureUrl: avatar
          job
          color
          email
      }
  `,
};

Here are some choices to be made. It seems there is some kind of convention used in most examples, but this convention is not explicit in the doc.

  • The key used on the User.fragments. Does it make sense to name it exactly like the propName user of the component?

  • The name of the fragment: it seems by convention people name it with the name of the component, and if useful, suffix them by the GraphQL type on which is the fragment. (here UserUser would probably be overkill suffixing).

I think it is good to follow the same convention across the same app, so that all fragment declarations are consistant. So, can someone more experienced help me clarify this convention that seems used in many Apollo examples?

Reducing fragment composition boilerplate ?

Lets consider now a Relationship component following the convention we've set up.

const Relationship = ({ user1, user2 }) => (
  <RelationshipContainer>
      <RelationshipUserContainer>
        <User user={user1} />
      </RelationshipUserContainer/>
      <RelationshipUserContainer>
        <User user={user2} />
      </RelationshipUserContainer/>
  </RelationshipContainer>
);
Relationship.fragments = {
  user1: gql`
      fragment RelationshipUser1User on User {
          ...User
      }
      ${User.fragments.user}
  `,
  user2: gql`
      fragment RelationshipUser2User on User {
          ...User
      }
      ${User.fragments.user}
  `,
};

Note that here I'm declaring 2 fragments that look the same. I think it's necessary because there are 2 props and you should not necessarily assume that the data requirement on both props are the same. We could easily imagine a component with me props, and friend props, where you would receive more data for the me props.

This works fine but it quite a lot of boilerplate and intermediate fragments that look quite unnecessary. Also it's not always convenient because from a component user point of view, you have to be aware of the 2 fragment names to be able to use it.

I tried to simplify this with the following

Relationship.fragments = {
  user1: User.fragments.user,
  user2: User.fragments.user,
};

This can work, but if you do this, then the fragment names are not anymore RelationshipUserXUser, but User instead, this means it breaks the encapsulation and that somehow you need to be aware that internally, the Relationship component is using the User component.

If one day, the Relationship component switch to using an alternative representation like UserAlt, this would require refactoring from all components using the Relationship fragments, which is something I'd like to avoid. I think in such a case, the modifications should only have to happen in the Relationship component.

Conclusion

I'd like to know the best practices to compose fragments with Apollo, so that components remain truly encapsulated, and preferably without involving too much boilerplate.

Am I already doing the right thing?

Is all this boilerplate unavoidable if I really want to compose queries?

like image 643
Sebastien Lorber Avatar asked Oct 17 '22 08:10

Sebastien Lorber


1 Answers

How about doing it like this:

const userFragment = gql`
  fragment Relationship_user on User {
    ...User_user
  }
  ${User.fragments.user}
`;
Relationship.fragments = {
  user1: userFragment,
  user2: userFragment,
};

Apart from that, I'd recommend you scope your fragment names as shown above, as some kind of name spacing is required, because otherwise it's more likely you'll run into using the same fragment names twice.

i.e.

  • User.fragments.user => User_user
  • Relationship.fragments.user => Relationship_user
like image 118
Kevin Smith Avatar answered Oct 21 '22 05:10

Kevin Smith