I've been working on implementing a generic <QueryRenderer/>
which serves as the single source of truth from the root level, ultimately branching off into multiple fragments. My initial implementation statically defined fragments within the query itself and would receive components as props like so...
...
class ComponentRenderer extends React.Component {
render() {
const ComponentRendererQuery = graphql`
query ComponentRendererQuery(
$globalId: ID
) {
viewer {
...Fragment1_viewer
...Fragment2_viewer
...Fragment3_viewer
...Etc_viewer
}
}
`
const Component = this.props.component;
return (
<QueryRenderer
environment={environment}
query={ ComponentRendererQuery }
variables={{
globalId: this.props.match.params.id
}}
render={({ error, props }) => {
if (error) {
return <div>{error.message}</div>
} else if (props) {
return <Component {...this.props} viewer={props.viewer} />
}
...
This setup was more or less working our very well for me as I didn't have <QueryRenderers/>
all over the place, just a personal preference, I found this setup to be a bit cleaner. I managed to overlook one detail however - All the fragments I've spread into the root level query were being requested. I was under the assumption that since the active React Component held the rest of the fragment that the others would be ignored, however this is not the case.
I believe this behavior is due to how the Relay pre-builds your data model, thus all of the fragments remain in scope. So my implementation was necessarily breaking my application, it was however incurring quite a bit of overfetching and creating a few exceptions in the server for any fragments that were dependent upon parameters ( in this example I am passing the GraphQL Global Identifier to generate a more detailed view on another page ).
So I tried another approach, thinking I could perhaps just predefined my queries and pass them into this generic <ComponentRenderer/>
which I have created.
const Fragment1Query = graphql`
query FragmentQuery {
viewer {
...Fragment1_viewer
}
}
`;
const Fragment2Query = graphql`
query FragmnetQuery {
viewer {
...Fragment2_viewer
}
}
`;
const hasId = this.props.match.params.id;
const Component = hasId ? Component1 : Component2
const Query = hasId ? Fragmen2 : Fragment2
return (
<ComponentRenderer
{...this.props}
component={Component}
query={Query}
/>
);
I have relocated the query here and assigned each previous fragment which was previously being spread into a single root query to a constant. Once against thinking I was being clever but not fully understanding what Relay was doing at build time.
So the problem is that Relay builds everything ahead of time which means whatever the state of the application remains as is. While logically I should be able to conditionally decide which fragment to present to the <QueryRenderer/>
, this does appear to work as one of the optimizations of Relay is dependent upon defining the structure of the GraphQL queries and fragments up front.
So somewhat at am impasse. I swear I have seen an example on GitHub somewhere floating around that accomplished what I am attempting but did not save the link. May have to just move the queries further down into other components... which actually might be more sensible as one of the intended designs for Relay was to enforce components to be in control of their own data needs.
Edit: Here is an example I came across today that addresses a similar problem in Relay Classic. Unfortunately so much has changed, this doesn't translate all that cleanly to Modern... but it gies me hope. Conditional fragments or embedded root-containers when using Relay with React-Native