Relay: [Modern] null in props when using fragments that query lists

Created on 21 Jun 2017  路  6Comments  路  Source: facebook/relay

Hi there,

here's my Query Renderer:

export default function ChooseTeam() {
  return (
    <QueryRenderer
      environment={environment}
      query={graphql`
        query ChooseTeamQuery {
          viewer {
            ...TeamSelector_allTeams
          }
        }
      `}
      render={({ props }) => {
        if (props) {
          return <TeamSelector allTeams={props.viewer.allTeams} />;
        }
        return <Loading />;
      }}
    />
  );
}

... and TeamSelector component:

class TeamSelector extends React.Component {
  render() {
    console.log(this.props);
    ...
  }
}

export default createFragmentContainer(TeamSelector, graphql`
  fragment TeamSelector_allTeams on Viewer {
    allTeams(first: 100) {
      edges {
        node {
          id
          name
        }
      }
    }
  }
`);

The problem is that <TeamSelector> receives null for allTeams property. API query is executed as expected and returns all necessary data, but the data is not passed to props. I was able to workaround this by moving allTeams(first: 100) one level up, to QueryRenderer. Another workaround that I have tried was using fragment alias, like teams: allTeams(first: 100) {... in child component, but it didn't work - Relay Compiler was throwing GraphQLError (however in GraphiQL that query worked fine).

I was unable to find any clues for this case in the docs, so I'm not sure if I'm doing something wrong, or is this a bug or just undocumented behavior.

Most helpful comment

You are passing the wrong thing to <TeamSelector>

Rather than this:

return <TeamSelector allTeams={props.viewer.allTeams} />;

Do this instead:

return <TeamSelector allTeams={props.viewer} />;

A handy way I have been thinking about it is that the ChooseTeam component should not know about the internal data declarations of TeamSelector. The only thing that ChooseTeam should know about the return shape of the graphql query is that there is a single top level viewer property that will contain some data (we shouldn't know what shape that data will take). <TeamSelector> will know how to deal with that data of unknown shape, so we'll delegate that data to <TeamSelector> to do its thing. Importantly, TeamSelector should be able to change its fragment as much as it wants (eg. it may decide to not even select the allTeams field) and this should not break ChooseTeam. If ChooseTeam was using knowledge of TeamSelector's fragment to break apart the graphql response, then changes in TeamSelector could break ChooseTeam. This would mean we couldn't work on components in isolation without the risk of breaking other parts of the system 馃憥 .

It's worth noting that Relay actually masks that data we shouldn't know about. This prevents us from accidentally reaching into parts of the graphql response that are not our concern.

All 6 comments

You are passing the wrong thing to <TeamSelector>

Rather than this:

return <TeamSelector allTeams={props.viewer.allTeams} />;

Do this instead:

return <TeamSelector allTeams={props.viewer} />;

A handy way I have been thinking about it is that the ChooseTeam component should not know about the internal data declarations of TeamSelector. The only thing that ChooseTeam should know about the return shape of the graphql query is that there is a single top level viewer property that will contain some data (we shouldn't know what shape that data will take). <TeamSelector> will know how to deal with that data of unknown shape, so we'll delegate that data to <TeamSelector> to do its thing. Importantly, TeamSelector should be able to change its fragment as much as it wants (eg. it may decide to not even select the allTeams field) and this should not break ChooseTeam. If ChooseTeam was using knowledge of TeamSelector's fragment to break apart the graphql response, then changes in TeamSelector could break ChooseTeam. This would mean we couldn't work on components in isolation without the risk of breaking other parts of the system 馃憥 .

It's worth noting that Relay actually masks that data we shouldn't know about. This prevents us from accidentally reaching into parts of the graphql response that are not our concern.

thank you for a quick response!

I completely agree with this approach - this is actually what I'm trying to do. I'm trying to avoid dependency between parent and child components. The code above illustrates just one of my numerous attempts to do that, I have tried a number of other combinations as well. The problem is that I can't get it working when a child fragment includes a request for a list, like allTeams(first: 100) { ... above. This approach worked perfectly for me with fragments that only included more properties of a node from a root query, but for an unknown reason it stops working when I add into a fragment something like allTeams(first: 100) ....

I have tried to pass viewer as you suggested, but I still get null in child component props

@stebunovd pass the viewer property down to the TeamSelector as so:

<TeamSelector viewer={props.viewer} />

and with TeamSelector,

class TeamSelector extends React.Component {
  render() {
    console.log(this.props.viewer);
    ...
  }
}

export default createFragmentContainer(TeamSelector, {
  viewer: graphql`
    fragment TeamSelector_viewer on Viewer {
      allTeams(first: 100) {
        edges {
          node {
            id
            name
          }
        }
      }
    }
`});

Note the object that wraps the graphql query and assigns it to the key viewer. This should let your props have access to it
I hope that works for you :)

thanks! I have tried exactly that, but unfortunately, it didn't work - I still got null in TeamSelector viewer prop.

UPD: I finally got it working! Not sure what was the reason though... I did some refactoring in my app (related to routing) and after that decided to give it another try. I have tried the same approach as before (passing viewer into child component) and this time it miraculously worked. Maybe I missed something in previous attempt...

thanks for help everyone!

I just spent quite a bit of time trying to figure out this exact same issue, google brought me here and this thread really helped me out! Thanks for the tips @kerrin-mathspace

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fedbalves picture fedbalves  路  3Comments

johntran picture johntran  路  3Comments

brad-decker picture brad-decker  路  3Comments

luongthanhlam picture luongthanhlam  路  3Comments

janicduplessis picture janicduplessis  路  3Comments