Tuesday, July 12, 2016

Getting Started with React, GraphQL and Relay (Part 2 of 2)

This article is part of a web development series from Microsoft. Thank you for supporting the partners who make SitePoint possible.

Introduction

The previous article explored the overall development process of working with Relay as well as the conversion of a GraphQL server. With the backend prepared, the second half of a React/GraphQL/Relay application is to consume it with the React-Relay web browser application. Consuming GraphQL services with React via Relay is tricky to setup. The technology is quite new, and therefore the number of examples, the clarity of the documentation, as well as a number of technical challenges (not bugs) have to be smoothed over. The example reviewed here will be a full blown CRUD (Create, Read, Update, and Delete) application. In addition to querying data, there will be an example of three kinds of mutations which will be helpful to new Relay developers because online examples of mutations are, at the time of this writing, few and far between.

Widget Tool Application

This simple application allows the user to manage a table of widgets. Widgets can be added, removed, and updated. The application does not use any kind of view routing which results in the Widget table being replaced with another view. Instead, it’s a simplistic version of an editable grid. For each widget the following information is collected:

  1. Name
  2. Description
  3. Color
  4. Size
  5. Quantity
  6. Owner

The drop-down list for the owner is populated from a distinct list of all owners who currently own widgets.

React and Relay

Relay applications are nothing more than React components wrapped in Relay Containers. The Relay Containers describe the data needed by the components, and Relay Root Containers connect with the GraphQL-Relay server to query and mutate data. The description of the data needs is accomplished via GraphQL fragments, while the root schema type for the connection is specified through a Relay Route. Conceptually, it’s a relatively simple setup. The challenge is in the details of the setup, and understanding exactly what Relay needs to know in order to perform the various operations.

React, graphicQL and Relay

Source: React – Facebook Code

The above image from this blog post depicts the way in which Relay works with React. Components trigger actions through Relay’s Store API, and the Relay Store handles the responses from the GraphQL server. Interestingly, Relay supports the concept of optimistic updates, which allow the mutation to be recorded in the store pending final confirmation from the server. Optimistic updates allow the UI to signal the user that the update has occurred, even though it has not. Should the update fail, the user will have to be notified, and the UI updated.

Querying Data

Querying data from a Relay-enabled GraphQL server is relatively simple, especially when compared to mutations. Once the component that will consume the data has been created, a Relay container describing the data needed by the component is created. To set up the container, the component being wrapped by it needs to be specified along with a query fragment which defines the data being queried.


import Relay from 'react-relay'; 
import WidgetToolComponent from '../components/widget-tool'; 
 
export default Relay.createContainer(WidgetToolComponent, { 
  fragments: { 
    colors: () => Relay.QL'fragment on __Type { enumValues { name description } }', 
    sizes: () => Relay.QL'fragment on __Type { enumValues { name description } }', 
    viewer: () => Relay.QL' 
      fragment on Viewer { 
        id 
        users(first: 1000) { 
          edges { 
            node { 
              id 
              firstName 
              lastName 
            } 
          } 
        } 
        widgets(first: 1000) { 
          edges { 
            node { 
              id 
              name 
              description 
              color 
              size 
              quantity 
              owner { 
                id 
                firstName 
                lastName 
              } 
            } 
          } 
        } 
      } 
    ' 
  } 
}); 

For this application, three fragments are needed. Two of the fragments are used for introspection queries, in order to load the possible values for two enums which are defined: colors and sizes. The third fragment is for querying application data for the specific viewer. In this application, there is only one unauthenticated viewer. In an application where authentication and authorization are implemented, the viewer would represent the authenticated user, and the data queried under the viewer would typically be limited to data accessible to the user. This limitation would need to be implemented in the GraphQL server.

The parameters passed into users and widgets are the GraphQL-Relay connection pagination arguments: first, after, last, before. first indicates how many edges to return from the beginning, or after the cursor if after is specified. last indicates how many edges to return up to the number of edges before the specified cursor. The cursor is used by Relay to determine the starting location.

Once the fragments are defined, the Relay route is used to specify the queries and fields to execute queries (including the fragments) against.


import Relay from 'react-relay'; 
 
export default class extends Relay.Route { 
  static queries = { 
    colors: () => Relay.QL'query { __type(name:"Color") }', 
    sizes: () => Relay.QL'query { __type(name:"Size") }', 
    viewer: () => Relay.QL'query { viewer }' 
  }; 
  static routeName = 'ViewerRoute'; 
} 

Once the Relay container and route, as well as the React component, are coded, they need to be configured with a Relay root container. The root container will query the GraphQL server for the data needed to fulfill the requirements of the Relay container. The query is formed using the Relay route and the Relay container fragments. Once the data is received, the root container will pass the data to the container, which will set the properties of the React component and render it.


import React from 'react'; 
import Relay from 'react-relay'; 
import ReactDOM from 'react-dom'; 
import WidgetToolContainer from './containers/widget-tool-container'; 
import ViewerRoute from './routes/viewer-route'; 
 
ReactDOM.render( 
  <Relay.RootContainer Component={WidgetToolContainer} route={new ViewerRoute()} />, 
  document.querySelector('main') 
); 

In this application, the root container will fill the WidgetToolContainer using data specified by the ViewerRoute and fragments defined within the WidgetToolContainer.

First Mutation: Insert

Setting up mutations requires creating a new class which inherits from Relay.Mutation, provided by the react-relay module.


import Relay from 'react-relay'; 
 
export default class extends Relay.Mutation { 
 
  static fragments = { 
    viewer: () => Relay.QL'fragment on Viewer { id }' 
  } 
 
  getMutation() { 
    return Relay.QL'mutation { insertWidget }'; 
  } 
 
  // more code here... 
 
} 

A static property named fragments needs to be configured in this new class. The fragments property lists the mutation’s data dependencies. In this case, the mutation of the widget is dependent upon the viewer ID. The mutation is dependent upon the viewer ID because all data retrieved in the application is retrieved through the context of the current viewer. Therefore, when the mutation is performed, Relay will ensure the viewer ID is always available.

With the fragments configured, the getMutation function needs to be configured for the mutation. The getMutation function specifies the name of the GraphQL mutation to be executed, and should match the name of the mutation on the GraphQL server.

The getVariables function transforms data passed into the constructor of the mutation into the structure needed by the GraphQL server to perform the mutation. The data passed into the constructor are made available on the props of the Relay mutation object. In this case, the widget fields on the props are used to construct a new widget object as a property of the input data object sent to the mutation on the GraphQL server. This widget object must conform to the InputInsertWidget type as defined by the GraphQL server.


  getVariables() { 
    return { 
      widget: { 
        name: this.props.name, 
        description: this.props.description, 
        color: this.props.color, 
        size: this.props.size, 
        quantity: this.props.quantity, 
        ownerId: this.props.ownerId 
      } 
    }; 
  } 

The props values are passed via the constructor for the mutation as shown here. The viewer must be set to the original viewer retrieved using Relay. The widget property must be set to null since this will be a new widget rather than an existing widget. Finally, using Object.assign, the properties from the widget object (which contains the data to be inserted) are copied on to the object being passed into the constructor.


Relay.Store.commitUpdate(new InsertWidgetMutation( 
  Object.assign({ viewer: this.props.viewer, widget: null }, widget) 
)); 

When commitUpdate is called, a new InsertWidgetMutation object is created and passed in. The function commitUpdate will use getConfigs and getFatQuery to determine which data needs to be updated to accomplish the mutation. While not shown here, it’s possible to configure optimistic updates and handle collisions between multiple mutations. These features really show the true power of Relay to handle data mutations intelligently.

For the insert widget mutation, the configuration type is RANGE_ADD, where a new widget is being added to the range of widget edges. The connection name identifies to which range of edges the widget is being added, and identifies the name of the GraphQL field which will contain the data for the new widget edge. The range behavior identifies how the widget will be added to the range–single or multiple range behaviors can be configured. For the widget connection, the default and only behavior is the append behavior. Other range operations are prepend, ignore, refetch, and remove.


getConfigs() { 
  return [{ 
    type: 'RANGE_ADD', 
    parentName: 'viewer', 
    parentID: this.props.viewer.id, 
    connectionName: 'widgets', 
    edgeName: 'widgetEdge', 
    rangeBehaviors: { 
      '': 'append' 
    } 
  }]; 
} 

getFatQuery is used to retrieve all of the data needed to update the application once the mutation has been completed. The name InsertWidgetPayload is simply 'Payload' appended to end of a camel case version of the mutation name. All GraphQL fields impacted by the mutation should be included in the fragment.


getFatQuery() { 
  return Relay.QL' 
    fragment on InsertWidgetPayload @relay(pattern: true) { 
      viewer { 
        widgets { 
          edges { 
            node { 
              id 
              name 
              description 
              color 
              size 
              quantity 
              owner { 
                id 
                firstName 
                lastName 
              } 
            } 
          } 
        } 
      } 
      widgetEdge 
    } 
  '; 
} 

Once the mutation is complete, Relay will update the Store, triggering React to render the component attached to the container. View the full source code of the insert widget mutation file on GitHub.

Continue reading %Getting Started with React, GraphQL and Relay (Part 2 of 2)%


by Eric Greene via SitePoint

No comments:

Post a Comment