GraphQL Client Best Practices Questions

Just clarifying that I did read all of Apollo Client Best Practices (and gotchas): Video Walkthrough before deciding to post this. This was a topic that I brought up originally in Discord and am moving over here for a cleaner and clearer conversation. For those brave enough to traverse the Discord history, here’s the original post: Discord

My question was:

Best practice question - if I want to add dynamic content to a layout, the solution is to make a new Cell and use that to fetch in the space I want to collect the data, right? Along those lines, if I’m having multiple cells on a single page - is Apollo then batching the request from the client to avoid crazy waterfalls or should I be doing a super query at the page root level and then adapting additional cells to query but it’ll leverage the cache because I did it as a super query? If going the super query route, should I use GQL fragments and how does Redwood support them?

To break this down, I really have a couple of branching questions around best practice of implementation.

Question 1: Adding dynamic content to a layout - are Cells the best practice?

Let’s say in my Routes.tsx, I have the following:

 <Router>
      <Set wrap={EventLayout}>
        <Route path="/events/{eventId}/sessions" page={EventSessions} name="eventSessions" />
      </Set>
  </Router>

My EventLayout will look like this:

const EventLayout = ({ children }) => {
  return (
    <>
      <nav>
        <EventTitleCell />
        <UserProfileDropdownCell />
      </nav>
      {children}
    </>
  );
};

Ideally, EventTitleCell pulls the event record’s title to display in my nav and the UserProfileDropdownCell will be like most websites where it shows your login info and gives you the menu to logout or explore your settings. My question is: is this considered the best practice in Redwood for dealing with dynamic data in layouts?

Question 2: Batching of GraphQL Queries

Having every Cell query the API is pretty inefficient if there’s overlap in data. For instance, in my above example, I could have a route at /events/{eventId} using the EventLayout that acts more like a dashboard that has the event info in the nav but also on the page with some other data that may also include the event’s info. All the sudden I have a lot of redundant data requests that I’d like to avoid. Is the default Redwood Apollo config set to leverage client batching?

If not, what is considered to be the best practice here? David’s recommendation in Discord was to leverage useQuery at the Layout layer. However, I don’t want to make a Layout per Page to achieve this. Ideally though, the Cells making up my pages could export query fragments and then the parent page/container could batch them as a single Query operation. The Cells could still perform the Query leveraging the Fragment and read from the cache but the parent making the big query would avoid the waterfalls I’m concerned about.

As an example, I could write my cell GQL like this:

// EventCell
export const QUERY = gql`
  query FindEventById($id: String!) {
    event: event(id: $id) {
      id
      ...EventCellFragment
    }
  }
`;

export const FRAGMENT = gql`
  fragment EventCellFragment on Event {
    id
    name
    description
    startAt
    endAt
  }
`

Then the page could have a parent query that runs on load that pulls all fragments from all children cells. The Cell is still performant reading from cache and the parent page could avoid the unnecessary waterfalls. Of course, if a fragment isn’t provided, then the current behavior would persist and you could still run waterfalls.

Note: it’s late and I’m little tired so I may have forgotten some key points that I’m carrying forward from Discord. Please call me out if something in this is confusing or doesn’t make sense - I’d love to update to clarify. :smile:

1 Like

Love this question!! And fragments can be even more powerful when they’re shared across cells (which is actually not yet supported by Redwood, and the type generator throws a gnarly error of you don’t then wrap your QUERY in a function).

So I think there’s a lot of work to be done here to support query batching - does someone else know if progress has been made on fragment support? Perhaps that’s part of this equation

Working on it!

See https://github.com/redwoodjs/redwood/pull/9140

Am just doing testing with union types as Apollo needs to be aware of all the possible types.

You are correct in that codegen currently fails if you do fragment t interpolation due to the use of the CodeFilerLoader.

Instead we’ll have a way to register fragments with the Apollo cache so the client knows about them.

2 Likes

I kept digging into RedwoodApooloProvider to see if I could extend it easily with a batch link. Looks like the answer is no unfortunately, but if the answer was yes, then adding batching could be introduced relatively simply. It would require https://github.com/redwoodjs/redwood/blob/main/packages/web/src/apollo/index.tsx#L222C9-L227 to be adjusted to include a BatchHttpLink. It might be relatively simple to include an escape hatch for those not wanting to opt-in too. I’m going to see about getting a PR together to make this a reality. :smile:

Well… sadly I got close but the API needs to enable batching support to make this functional :slightly_frowning_face:

Unless I’m missing something obvious, this should be sufficient to enable batching: add batching attempt · dustinsgoodman/event-platform@05a50aa · GitHub

I’ll need to investigate more another night :sweat_smile:

Update: Looks like if we could enable batching in yoga or pass a config to it then this would be solved per Batching – GraphQL Yoga. Reading the Redwood source and docs, I cannot for the life of me figure out if it’s possible to pass config to Yoga. If this is possible and someone can point me in a direction to try it out, I’ll have working solution soon™.

1 Like

Hi,
concerning the usage of a Cell in Layout and then using it in its children routes, I am using hooks and context to consume state in children:

Works out pretty well so far.

1 Like