Search code examples
reactjsrelayjsrelayrelaymodern

How to get the QueryRenderer to propagate parent prop changes?


Using Relay Modern v.1.4.1, consider the following List component.

It takes two props foo and bar and renders a Table component with these props and the result of the GraphQL query:

class List extends React.Component {

  constructor(props) {
    super(props);
    // I accept this.props.foo and this.props.bar
    this.renderQuery = this.renderQuery.bind(this);
  }

  render() {
    return <QueryRenderer
      environment={this.props.relayEnvironment}
      query={SomeQuery}
      variables={{
        count: 20,
      }}
      render={this.renderQuery}
    />;
  }

  renderQuery(readyState) {
    console.log("List component props:");
    console.log(this.props);
    return <Table 
      foo={this.props.foo} bar={this.props.bar} 
      data={readyState.data}
    />
  }

}

Now, for the initial rendering of the Table component foo and bar are set correctly, however if foo or bar change afterwards the new values are not propagated to the Table component because of the QueryRenderer.

Since the QueryRenderer only re-renders if the variables for SomeQuery change the renderQuery method is not triggered because the QueryRenderer does not see a need for it, so the Table component stays with the old prop values.

Since passing down props to children is a very common task the fact that the QueryRenderer is oblivious to the parent's prop changes poses quite a problem. I've been wondering what the solution for this might be and came up with some options:

  1. Use Context to bypass this behaviour. However it is generally discouraged to use Context unless you have good reasons for it + it feels like a dirty hack to circumvent the issue.
  2. Force the QueryRenderer to re-render with the same readyState as before. This could perhaps be realized using refs but you would need to check when to re-render by yourself.
  3. Somehow make the QueryRenderer care about my props. This is obviously the right way but I didn't find anything in the documentation that allows this, so I would need to do something like creating a Higher Order Component from the QueryRenderer or a subclass of it that implements this behavious.

Ideally I would like to have a native way to make the QueryRenderer look at prop changes, something like

<QueryRenderer
  environment={this.props.relayEnvironment}
  query={SomeQuery}
  variables={{
    count: 20,
  }}
  render={this.renderQuery}
  propagateProps={this.props}
/>

or

<QueryRenderer
  environment={this.props.relayEnvironment}
  query={SomeQuery}
  variables={{
    count: 20,
  }}
  render={this.renderQuery}
  {...this.props}
/>

Any ideas?


Solution

  • Remove your Class component and change the above code to:

    const List = (parentProps) => (
      <QueryRenderer
        environment={parentProps.relayEnvironment}
        query={SomeQuery}
        variables={{
          count: 20,
        }}
        render={({ props }) => (
          <Table 
            foo={this.parentProps.foo} bar={this.parentProps.bar} 
            data={props.data}
          />
        )}
      />
    )
    

    QueryRenderer is already a React Component in itself and you normally do not have to wrap it in a class Component render.

    List is now a Pure function component and when it receives new props (parentProps) it will re-render, hence Table with be re-rendered