3

I have parent component that ends up with nested fragments looking like this:

query MyAppQuery(
    $id
    $a
    $b
    $c
) {
    viewer {
      ...App_viewer
      ...ComponentA_viewer @include(if: $a)
      ...ComponentB_viewer @include(if: $b)
      ...ComponentC_viewer @include(if: $c)
    }
    allEmployees: allUsers(userType: "1") {
        ...ComponentA_allEmployees @include(if: $a)
        ...ComponentB_allEmployees @include(if: $b)
        ...ComponentC_allEmployees @include(if: $c)
    }
};

Relay fails if I don't include all this child fragments but the data is the same for all these, it seems pretty dumb having to declare a view fragment on all my child components that require the signed in user.

How can I request this piece of data at the top of my application and have it available to child components without having to include the all these fragments.

This is starting to feel like reverse prop drilling with I have to declare a fragment at the lower end of my app and pass it up the chain.

Same with allEmployees. It's the same data that I should only get once and pass down or access through context but I have to pass in all these stupid fragments or relay complains.

Batman
  • 5,563
  • 18
  • 79
  • 155

1 Answers1

1

This is a core pattern to Relay, and despite the verbosity, it is actually highly encouraged.

In Relay, child components are encouraged to specify their data requirements on their own in the form of fragments.

Ultimately, you're going to end up accumulating a lot of fragments that get spread elsewhere in your app, so it's worth pointing out a few key Relay features about why this is good:

  1. Relay will not send duplicate requests to your API if you declare several fragments next to each other that request the same field(s). Instead, they will all get fetched once, in one round-trip. You don't have to worry about introducing overfetching/duplicate query problems, because they don't exist in Relay.

  2. Relay introduces a compilation step via the Relay Compiler, that intelligently analyzes your GraphQL schema and any graphql template tags that you define in your code. This generates artifacts, which help manage fetching data and updating the Relay store automagically, so you don't have to. By declaring a lot of fragments, you are effectively telling the compiler and store about the data requirements of your components, even if they are the same/similar. Duplication here is what makes Relay great.

  3. Your QueryRenderer ancestor at the root of the tree will handle the actual fetching, and the fragments you've defined on child components lower in the tree instruct the Relay Compiler and Store where to send the data once it is fetched. This follows from #2.

So, in short, to make the most of Relay, declare your components' data requirements with fragments, and let Relay do the heavy lifting, and don't worry about duplication and lack of reusability. It is to your advantage.

JosephHall
  • 631
  • 6
  • 8
  • Is there an alternative pattern? For example, the login user is something I know I will need in many different pieces of my app. Either for mutation or filtering. Should I be using react Context to store it at the top or including it in all my needed fragments? – Batman Nov 29 '20 at 02:14
  • @Batman Relay discourages reusing fragments (the relay compiler will cry if you try) especially given how it compiles your fragments into artifacts that it uses to intelligently cache data in the store for later. – JosephHall Nov 29 '20 at 02:42
  • @Batman However, that doesn't mean you can't preload a query that your app needs and access that data elsewhere. Imagine you have a `useAuthedUser` hook which reads data from a context about the current authentication state of the user. Then, you could simply declare your relay fragment dependencies _inside_ of that context provider that wraps everything else in your app. In this case, you can get away with this architecture in relay because you are effectively describing the data requirements of the context provider. – JosephHall Nov 29 '20 at 02:44
  • I like this approach, so essentially I can have a QueryRenderer inside the AppProvider, and inside that renderer, include anything that I think is important globally. From there I can access it anywhere in the app. I'll try this. Thank you for the suggestion. – Batman Nov 29 '20 at 03:29