Edit on GitHub

RelayContainer

RelayContainer is a higher-order React component that lets a React component encode its data requirements.

  • Relay ensures that this data is available before the component is rendered.
  • Relay updates the component whenever the underlying data has changed.

Relay containers are created using Relay.createContainer.

Overview #

Container Specification

Properties and Methods

These are the methods and properties that the container will provide as this.props.relay in the plain React component.

Static Methods

Container Specification #

fragments #

fragments: RelayQueryFragments<Tk> = {
  [propName: string]: (
    variables: {[name: string]: mixed}
  ) => Relay.QL`fragment on ...`
};

Containers declare data requirements on fragments using GraphQL fragments.

Only fields specified by these fragments will be populated in this.props when the component is rendered. This ensures that there are no implicit dependencies from a component on its parent component or any child components.

Example #

class StarWarsShip extends React.Component {
  render() {
    return <div>{this.props.ship.name}</div>;
  }
}

module.exports = Relay.createContainer(StarWarsShip, {
  fragments: {
    ship: () => Relay.QL`
      fragment on Ship {
        name
      }
    `,
  },
});

In this example, the fields associated with the ship fragment will be made available on this.props.ship.

See also: Containers > Relay Containers

initialVariables #

initialVariables: {[name: string]: mixed};

The initial set of variable values available to this component's fragments.

Example #

class ProfilePicture extends React.Component {...}

module.exports = Relay.createContainer(ProfilePicture, {
  initialVariables: {size: 50},
  fragments: {
    user: () => Relay.QL`
      # The variable defined above is available here as '$size'.
      # Any variable referenced here is required to have been defined in initialVariables above.
      # An 'undefined' variable value will throw an 'Invariant Violation' exception.
      # Use 'null' to initialize unknown values.
      fragment on User { profilePicture(size: $size) { ... } }
    `,
  },
});

In this example, profilePicture(size: 50) will be fetched for the intial render.

prepareVariables #

prepareVariables: ?(
  prevVariables: {[name: string]: mixed}
) => {[name: string]: mixed}

Containers can define a prepareVariables method which provides the opportunity to modify the variables that are available to fragments. The new variables can be generated based on the previous variables (or the initialVariables if no previous ones exist) in addition to the runtime environment.

This method is also called after the partial set of variables from setVariables has been applied. The variables returned are used to populate the fragments.

Example #

module.exports = Relay.createContainer(ProfilePicture, {
  initialVariables: {size: 50},
  prepareVariables: prevVariables => {
    return {
      ...prevVariables,
      // If devicePixelRatio is `2`, the new size will be `100`.
      size: prevVariables.size * window.devicePixelRatio,
    };
  },
  // ...
});

shouldComponentUpdate #

shouldComponentUpdate: () => boolean;

RelayContainer implements a conservative default shouldComponentUpdate that returns false if no fragment props have changed and all other props are equal scalar values. This may block updates to components that receive data via context. To ensure an update in this case override the default behavior by specifying a shouldComponentUpdate function.

Example #

module.exports = Relay.createContainer(ProfilePicture, {
  shouldComponentUpdate: () => true,
  // ...
});

Properties and Methods #

The properties and methods listed below can be accessed on this.props.relay from the wrapped React component.

route #

route: RelayRoute

Route is useful in providing the context which a component is being rendered in. It includes information about the name, params, and queries of the current route.

Example #

var name = this.props.relay.route.name;
if (name === 'SuperAwesomeRoute') {
  // Do something super cool.
}

See also: Routes

variables #

variables: {[name: string]: mixed}

variables contains the set of variables that was used to fetch the current set of props.

Example #

class ProfilePicture extends React.Component {
  render() {
    var user = this.props.user;
    return (
      <View>
        <Image
          uri={user.profilePicture.uri}
          width={this.props.relay.variables.size}
        />
      </View>
    );
  }
}
module.exports = Relay.createContainer(ProfilePicture, {
  initialVariables: {size: 50},
  fragments: {
    user: () => Relay.QL`
      fragment on User { profilePicture(size: $size) { ... } }
    `,
  },
});

In this example, the width of the rendered image will always correspond to the $size variable used to fetch the current version of profilePicture.uri.

Note

Never mutate this.props.relay.variables directly as it will not trigger data to be fetched properly. Treat this.props.relay.variables as if it were immutable, just like props.

pendingVariables #

pendingVariables: ?{[name: string]: mixed}

pendingVariables contains the set of variables that are being used to fetch the new props, i.e. when this.props.relay.setVariables() or this.props.relay.forceFetch() are called and the corresponding request is in flight.

If no request is in flight pendingVariables is null.

Example #

class ProfilePicture extends React.Component {
  requestRandomPictureSize = () => {
    const randIntMin = 10;
    const randIntMax = 200;
    const size = (Math.floor(Math.random() * (randIntMax - randIntMin + 1)) + randIntMin);
    this.props.relay.setVariables({size});
  }

  render() {
    const {relay, user} = this.props;
    const {pendingVariables} = relay;
    if (pendingVariables && 'size' in pendingVariables) {
      // Profile picture with new size is loading
      return (
        <View>
          <LoadingSpinner />
        </View>
      )
    }

    return (
      <View>
        <Image
          uri={user.profilePicture.uri}
          width={relay.variables.size}
        />
        <button onclick={this.requestRandomPictureSize}>
          Request random picture size
        </button>
      </View>
    );
  }
}
module.exports = Relay.createContainer(ProfilePicture, {
  initialVariables: {size: 50},
  fragments: {
    user: () => Relay.QL`
      fragment on User { profilePicture(size: $size) { ... } }
    `,
  },
});

In this example, whenever a picture with a new size is being loaded a spinner is displayed instead of the picture.

setVariables #

setVariables([partialVariables: Object, [onReadyStateChange: Function]]): void

Components can change their data requirements by using setVariables to request an update to the current set of variables.

this.props.relay.setVariables can be called to update a subset or all of the variables at the same time. In return, Relay will use the new variables to attempt to fulfill the new fragment. This may involve sending a request to the server if data is not already available on the client.

An optional onReadyStateChange callback can be supplied to respond to the events involved with the data fulfillment.

Example #

class Feed extends React.Component {
  render() {
    return (
      <div>
        {this.props.viewer.feed.edges.map(
          edge => <Story story={edge.node} key={edge.node.id} />
        )}
      </div>
    );
  }
  _handleScrollLoad() {
    // Increments the number of stories being rendered by 10.
    this.props.relay.setVariables({
      count: this.props.relay.variables.count + 10
    });
  }
}
module.exports = Relay.createContainer(Feed, {
  initialVariables: {count: 10},
  fragments: {
    viewer: () => Relay.QL`
      fragment on Viewer {
        feed(first: $count) {
          edges {
            node {
              id,
              ${Story.getFragment('story')},
            },
          },
        },
      }
    `,
  },
});

Note

setVariables does not immediately mutate variables, but creates a pending state transition. variables will continue returning the previous values until this.props has been populated with data that fulfills the new variable values.

See also: Containers > Requesting Different Data, Ready State

forceFetch #

forceFetch([partialVariables: Object, [onReadyStateChange: Function]]): void

forceFetch is similar to setVariables because it is also used to change the data requirements by altering variables.

The two methods differ in that instead of sending a query that includes only fields missing from the client, forceFetch sends a request to refetch each and every fragment. This ensures that the props for the component are freshly fetched from the server.

An optional onReadyStateChange callback can be supplied to respond to the events involved with the data fulfillment.

Note

forceFetch can be called with an empty set of partial variables, meaning it can trigger a refresh of the currently rendered set of data.

See also: Ready State

hasOptimisticUpdate #

hasOptimisticUpdate(record: Object): boolean

Calling hasOptimisticUpdate with a record from this.props will return whether that given record is affected by an optimistic mutation. It allows the component to render local optimistic changes differently from data that has successfully synchronized with the server.

Example #

class Feed extends React.Component {
  render() {
    var edges = this.props.viewer.feed.edges;
    return (
      <div>
        {edges.map(edge => {
          var node = edge.node;
          if (this.props.relay.hasOptimisticUpdate(node)) {
            // Render pending story that has not been stored
            // on the server using a diffrent component.
            return (
              <PendingStory
                key={edge.node.id}
                story={edge.node}
              />
            );
          } else {
            return (
              <Story
                key={edge.node.id}
                story={edge.node}
              />
            );
          }
        })}
      </div>
    );
  }
}

module.exports = Relay.createContainer(Feed, {
  initialVariables: {count: 10},
  fragments: {
    viewer: () => Relay.QL`
      fragment on Viewer {
        feed(first: $count) {
          edges {
            node {
              id,
              ${Story.getFragment('story')},
              ${PendingStory.getFragment('story')}
            }
          }
        }
      }
    `,
  },
});

See also: Mutations > Optimistic Updates

getPendingTransactions #

getPendingTransactions(record: Object): ?Array<RelayMutationTransaction>

Components can inspect pending mutations on any record (i.e. data made available in props with a corresponding fragment). Calling getPendingTransactions with a record will return a list of the pending mutation transactions that affect that particular record.

Each RelayMutationTransaction has methods to check the status of the mutation and provide ways to rollback or resend the mutation as needed.

Example #

class Story extends React.Component {
  render() {
    var story = this.props.story;
    var transactions = this.props.relay.getPendingTransactions(story);
    // For this example, assume there is only one transaction.
    var transaction = transactions ? transactions[0] : null;
    if (transaction) {
      // Display an error message with a retry link if a mutation failed.
      if (transaction.getStatus() === 'COMMIT_FAILED') {
        return (
          <span>
            This story failed to post.
            <a onClick={transaction.recommit}>Try Again.</a>
          </span>
        );
      }
    }
    // Render story normally.
  }
}

module.exports = Relay.createContainer(ProfilePicture, {
  fragments: {
    story: () => Relay.QL`
      fragment on story {
        # ...
      }
    `,
  },
});

RelayMutationTransaction.getStatus can return one of the following strings:

  • UNCOMMITTED — Transaction hasn't yet been sent to the server. Transaction can be committed or rolled back.
  • COMMIT_QUEUED — Transaction was committed but another transaction with the same collision key is pending, so the transaction has been queued to send to the server.
  • COLLISION_COMMIT_FAILED — Transaction was queued for commit but another transaction with the same collision key failed. All transactions in the collision queue, including this one, have been failed. Transaction can be recommitted or rolled back.
  • COMMITTING — Transaction is waiting for the server to respond.
  • COMMIT_FAILED — Transaction was sent to the server for comitting but failed.

Static Methods #

getFragment #

getFragment(
  fragmentName: string, 
  variables?: {[name: string]: mixed}
): RelayFragmentReference

Gets a reference to a child container's fragment for inclusion in a parent fragment.

Example #

Fragment composition is achieved via ES6 template string interpolation and getFragment:

// Parent.js
Relay.createContainer(Parent, {
  fragments: {
    parentFragment: () => Relay.QL`
      fragment on Foo {
        id
        ${Child.getFragment('childFragment')}
      }
    `,
  }
});
// Child.js
Relay.createContainer(Child, {
  initialVariables: {
    size: 64,
  },
  fragments: {
    childFragment: () => Relay.QL`
      fragment on Foo {
        photo(size: $size) { uri }
      }
    `,
  }
});

In this example, whenever Parent is fetched, Child's fragment will also be fetched. When rendering, <Parent> will only have access to the props.foo.id field; data from the child fragment will be masked. By default, childFragment will use its corresponding initial variables. Relay will fetch photo(size: 64). When <Child> is rendered it will also make the initial variables available as props.relay.variables = {size: 64}.

Overriding Fragment Variables #

Sometimes a parent needs to override the default variables of a child component. Imagine that we want to render Child above with a photo size of 128 instead of the default 64. To do this, we have to ensure that both the fragment and the container know about the custom variable. To set a custom variable in the query, use the second argument to getFragment:

// Parent.js
Relay.createContainer(Parent, {
  fragments: {
    parentFragment: () => Relay.QL`
      fragment on Foo {
        id
        ${Child.getFragment('childFragment', {size: 128})}
      }
    `,
  }
});

Now Relay will fetch the photo with size 128 - but the Child container won't magically know about this variable. We have to tell it by passing the variable value as a prop:

const Parent = (props) => {
  return (
    <Child 
      childFragment={props.parentFragment} 
      size={128} 
    />;
  );
}

Now Relay will both fetch the larger photo size and Child will know to render it.