Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Relay Modern RefetchContainer props aren't passed to component

I'm having some issues setting up a refetchContainer in Relay Modern. A parent component is the QueryRenderer, which runs an initial query, populating the child component's props appropriately (a-prop-riately? eh? eh?!) . The refetchContainer specifies all our variables, and on an input field's onChange event, re-runs a query with the new variables. This all works perfectly, except that the child's props are never updated with the new data received. I can drill down the Relay store and see that the query was indeed received with the appropriate data. Been bangin' my head against this for a while and I would appreciate some help. Probably something simple I'm missing. And Lord knows Relay Modern documentation is sparse.

I've poked around and can't find an appropriate solution. This guy seems to be having a similar issue: relay refetch doesn't show the result

The parent component with QueryRenderer:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { graphql, QueryRenderer } from 'react-relay';
import Search from './Search';

const propTypes = {
  auth: PropTypes.object.isRequired,
};

class SearchContainer extends Component {
  render() {
    return (
      <QueryRenderer
        query={graphql`
          query SearchContainerQuery($search: String!){
            users: searchUsers(search:$search, first:10){
              ...Search_users
            }
          }`}
        variables={{ search: 'someDefaultSearch' }}
        environment={this.props.auth.environment}
        render={({ error, props }) => {
          if (error) {
            console.log(error);
          }
          if (props) {
            return <Search users={props.users} />;
          }
          return <div>No Dice</div>;
        }}
      />
    );
  }
}

SearchContainer.propTypes = propTypes;

export default connect(state => ({ auth: state.auth }))(SearchContainer);

The child component with createRefetchContainer:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { createRefetchContainer, graphql } from 'react-relay';

const propTypes = {
  relay: PropTypes.object.isRequired,
  users: PropTypes.object,
};

const defaultProps = {
  users: {},
};

class Search extends Component {
  render() {
    return (
      <div>
        <input
          type="text"
          onChange={(e) => {
            e.preventDefault();
            this.props.relay.refetch({
              search: e.target.value,
            });
          }}
        />
        <ul>
          {this.props.users.nodes.map(user =>
            <li key={user.id}>{user.username}</li>,
          )}
        </ul>
      </div>
    );
  }
}

Search.propTypes = propTypes;
Search.defaultProps = defaultProps;

export default createRefetchContainer(
  Search,
  {
    users: graphql.experimental`
      fragment Search_users on SearchUsersConnection
      @argumentDefinitions(
        search: {type: "String", defaultValue: ""}
      ) {
        nodes {
            id
            displayName
            username
          }
      }
    `,
  },
  graphql.experimental`
    query SearchRefetchQuery($search: String!) {
      users: searchUsers(search:$search, first:10){
        ...Search_users @arguments(search: $search)
      }
    }
  `,
);

GraphQL looks like this:

# A connection to a list of `User` values.
type SearchUsersConnection {
  # Information to aid in pagination.
  pageInfo: PageInfo!

  # The count of *all* `User` you could get from the connection.
  totalCount: Int

  # A list of edges which contains the `User` and cursor to aid in pagination.
  edges: [SearchUsersEdge]

  # A list of `User` objects.
  nodes: [User]
}

Network calls are made appropriately, and data is returned as expected. NetworkCalls

It seems the @arguments directive can be left out of the refetch query here:

query SearchRefetchQuery($search: String!) {
      users: searchUsers(search:$search, first:10){
          ...Search_users @arguments(search: $search)
      }
}

(removing it seems to have no effect)

I've tried adding the @arguments directive to the parent component's fragment as per the recommendation here: Pass variables to fragment container in relay modern, to no effect.

like image 334
cntrlz Avatar asked Jul 21 '17 19:07

cntrlz


2 Answers

As mentioned in other answers, this is actually expected behavior, and I'll try to expand on the answer a little bit.

When refetch is called and the refetchQuery is executed, Relay doesn't actually use the result of the query to re-render the component. All it does is normalize the payload into the store and fire any relevant subscriptions. This means that if the fetched data is unrelated to the data that the mounted container is subscribed to (e.g. using a totally different node id that doesn't have any data overlaps), then the component won't re-render.

In this specific scenario, the reason why the container doesn't re-render, i.e. why it isn't subscribed to the changes fired by the refetchQuery, is due to how subscriptions are set up. A subscription is basically a subscription to a snapshot, which basically represents a concrete fragment and the data and records associated with it at a given point in time -- when the records associated with a snapshot change, the subscription for that snapshot will be notified of changes.

In this case, given that the fragment for the container has no variables, the snapshot that it will subscribe to will only be associated with the initial node; after refetch happens and the the store is updated, the records for the new node will have been updated, but the record associated with that original snapshot hasn't changed, so the container subscribed to it won't be notified.

This means that Refetch containers are only really meant to be used when you are changing variables in the component fragment. The solutions provided here are valid, i.e. including variables in the fragment for the refetch container or setting new variables one level up in the QueryRenderer.

This could definitely be made more clear in the docs, so I'll update them to reflect this. I hope this helps!

like image 153
jstejada Avatar answered Nov 20 '22 16:11

jstejada


Oh I think I got through a similar problem. If I remember correctly I believe the "id" Relay is using to identify the component to pass the props to, is including the variables for the initial query so try unwrapping your container fragment from the QueryRenderer's query.

The setup that worked for me is something like the following:

<QueryRenderer
  variables={{search: 'someDefaultSearch', count: 10}}
  query={graphql`
    query SearchContainerQuery($search: String!, $count: Int!){
      # if we want defaults we need to "prepare" them from here
      ...Search_users @arguments(search: $search, count: $count)
    }
  `}
  environment={this.props.auth.environment}
  render={({ error, props: relayProps }) => {
    // I use relayProps to avoid possible naming conflicts
    if (error) {
      console.log(error);
    }
    if (relayProps) {
      // the users props is "masked" from the root that's why we pass it as is
      return <Search users={relayProps} />;
    }
    return <div>No Dice</div>;
  }}
/>

And the refetchContainer would look something like:

export default createRefetchContainer(
  Search,
  {
    users: graphql`
      # In my schema the root is called RootQueryType 
      fragment Search_users on RootQueryType
      @argumentDefinitions(
        search: {type: "String"}
        count: {type: "Int!"}
      ) {
        searchUsers(search: $search, first: $count) {
          nodes {
            id
            displayName
            username
          }
        }
      }
    `
  },
  graphql`
    query SearchRefetchQuery($search: String!, $count: Int!) {
      ...Search_users @arguments(search: $search, count: $count)
    }
  `
);

Notice that the above example assumes Relay v1.3 where graphql.experimental is deprecated. Also, I don't remember if with the @arguments trick it's possible to make your approach of aliasing the searchUsers work.

My last suggestion is turning your debugger on and see whats going on when you get the data.

Finally, as per your comments, I agree this might be a BUG. Let's see how things evolve in the issue you reported.

like image 36
hisa_py Avatar answered Nov 20 '22 15:11

hisa_py