Version: v1.6.0

Fragment Container

A Fragment Container is a higher-order component that allows components to specify their data requirements. A container does not directly fetch data, but instead declares a specification of the data needed for rendering, and then Relay will guarantee that this data is available before rendering occurs.

Table of Contents:

createFragmentContainer#

createFragmentContainer has the following signature:

createFragmentContainer(
component: ReactComponentClass,
fragmentSpec: GraphQLTaggedNode | {[string]: GraphQLTaggedNode},
): ReactComponentClass;

Arguments#

  • component: The React Component class of the component requiring the fragment data.
  • fragmentSpec: Specifies the data requirements for the Component via a GraphQL fragment. The required data will be available on the component as props that match the shape of the provided fragment. fragmentSpec can be one of 2 things:
    • A graphql tagged fragment. If the fragment uses the name convention <FileName><...>_<propName>, the fragment's data will be available to the Component as a prop with the given <propName>. If the fragment name doesn't specify a prop name, the data will be available as a data prop.
    • An object whose keys are prop names and values are graphql tagged fragments. Each key specified in this object will correspond to a prop in the resulting Component.
    • Note: To enable compatibility mode, relay-compiler enforces fragments to be named as <FileName>_<propName>.

Available Props#

The Component resulting from createFragmentContainer will receive the following props:

type Props = {
relay: {
environment: Environment,
},
// Additional props as specified by the fragmentSpec
}

Example#

To start, let's build the plain React version of a hypothetical <TodoItem /> component that displays the text and completion status of a Todo.

React Component#

Here's a basic implementation of <TodoItem /> that ignores styling in order to highlight the functionality:

// TodoItem.js
class TodoItem extends React.Component {
render() {
// Expects the `item` prop to have the following shape:
// {
// item: {
// text,
// isComplete
// }
// }
const item = this.props.item;
return (
<View>
<Checkbox checked={item.isComplete} />
<Text>{item.text}</Text>
</View>
);
}
}

Data Dependencies With GraphQL#

In Relay, data dependencies are described using GraphQL. For <TodoItem />, the dependency can be expressed as follows. Note that this exactly matches the shape that the component expected for the item prop.

graphql`
# This fragment only applies to objects of type 'Todo'.
fragment TodoItem_item on Todo {
text
isComplete
}
`

Defining Containers#

Given the plain React component and a GraphQL fragment, we can now define a Fragment Container to specify this component's data requirements. Let's look at the code first and then see what's happening:

// TodoItem.js
import {createFragmentContainer, graphql} from 'react-relay';
class TodoItem extends React.Component // as above
// Export a *new* React component that wraps the original `<TodoItem>`.
export default createFragmentContainer(TodoItem, {
// For each of the props that depend on server data, we define a corresponding
// key in this object. Here, the component expects server data to populate the
// `item` prop, so we'll specify the fragment from above at the `item` key.
item: graphql`
fragment TodoItem_item on Todo {
text
isComplete
}
`,
});

Relay will infer the prop name from the fragment name according to the fragment naming convention <FileName><...>_<propName>. The example below is equivalent to the one above:

export default createFragmentContainer(
TodoItem,
graphql`
fragment TodoItem_item on Todo {
text
isComplete
}
`,
);

If there is no _<propName> suffix, the data prop name will be used:

class TodoItem extends React.Component {
render() {
const item = this.props.data;
// ...
}
}
export default createFragmentContainer(
TodoItem,
graphql`
fragment TodoItem on Todo {
text
isComplete
}
`,
);

Container Composition#

React and Relay support creating arbitrarily complex applications through composition. Larger components can be created by composing smaller components, helping us to create modular, robust applications.

Let's explore how this works via a <TodoList /> component that composes the <TodoItem /> we defined above.

Composing Views#

View composition is exactly what you're used to โ€” Relay containers are just standard React components. Here's the <TodoList /> component:

class TodoList extends React.Component {
render() {
// Expects a `list` with a string `title`, as well as the information
// for the `<TodoItem>`s (we'll get that next).
const list = this.props.list;
// It works just like a React component, because it is one!
return (
<View>
<Text>{list.title}</Text>
{list.todoItems.map(item => <TodoItem item={item} />)}
</View>
);
}
}

Composing Fragments#

Fragment composition works similarly โ€” a parent container's fragment composes the fragment for each of its children. In this case, <TodoList /> needs to fetch information about the Todos that are required by <TodoItem />.

class TodoList extends React.Component // as above
export default createFragmentContainer(
TodoList,
// This `_list` fragment name suffix corresponds to the prop named `list` that
// is expected to be populated with server data by the `<TodoList>` component.
graphql`
fragment TodoList_list on TodoList {
# Specify any fields required by '<TodoList>' itself.
title
# Include a reference to the fragment from the child component.
todoItems {
...TodoItem_item
}
}
`,
);

Note that when composing fragments, the type of the composed fragment must match the field on the parent in which it is embedded. For example, it wouldn't make sense to embed a fragment of type Story into a parent's field of type User. Relay and GraphQL will provide helpful error messages if you get this wrong (and if they aren't helpful, let us know!).

Passing Arguments to a Fragment#

@argumentDefinitions#

When defining a fragment, you can use the @argumentDefinitions directive to specify any arguments, with potentially default values, that the fragment expects.

For example, let's redefine our TodoList_list fragment to take some arguments using @argumentDefinitions:

fragment TodoList_list on TodoList @argumentDefinitions(
count: {type: "Int", defaultValue: 10}, # Optional argument
userID: {type: "ID"}, # Required argument
) {
title
todoItems(userID: $userID, first: $count) { # Use fragment arguments here as variables
...TodoItem_item
}
}

Any arguments defined inside @argumentDefinitions will be local variables available inside the fragment's scope. However, a fragment can also reference global variables that were defined in the root query.

@arguments#

In order to pass arguments to a fragment that has @argumentDefinitions, you need to use the @arguments directive.

Following our TodoList_list example, we would pass arguments to the fragment like so:

query TodoListQuery($count: Int, $userID: ID) {
...TodoList_list @arguments(count: $count, userID: $userID) # Pass arguments here
}

Calling Component Instance Methods#

React component classes may have methods, often accessed via refs. Since Relay composes these component instances in a container, you need to use the componentRef prop to access them:

Consider an input with a server-defined placeholder text and an imperative method to focus the input node:

export default createFragmentContainer(
class TodoInput extends React.Component {
focus() {
this.input.focus();
}
render() {
return <input
ref={node => { this.input = node; }}
placeholder={this.props.data.suggestedNextTitle}
/>;
}
},
graphql`
fragment TodoInput on TodoList {
suggestedNextTitle
}
`,
);

To call this method on the underlying component, first provide a componentRef function to the Relay container. This differs from providing a ref function which would provide a reference to the Relay container itself, not the underlying React Component.

export default createFragmentContainer(
class TodoListView extends React.Component {
render() {
return <div onClick={() => this.input.focus()}>
<TodoInput
data={this.props.data}
componentRef={ref => { this.input = ref; }}
/>
</div>;
}
},
graphql`
fragment TodoListView on TodoList {
...TodoInput
}
`,
);

Rendering Containers#

As we've learned, Relay fragment containers only declare data requirements as GraphQL fragments. In order to actually fetch and render the specified data, we need to use a QueryRenderer component to render a root query and any fragment containers included within. Please refer to our QueryRenderer docs for more details.

Last updated on by Davis Robertson