# Relay Tutorial: Creating pages

Step 1: Create relay-todolist/src/routes/AllTodos.js

We will now define a page that displays all the Todo items stored in our todos table.

Create a file relay-todolist/src/routes/AllTodos.js and paste the following code:


import React from 'react';
import Relay from 'react-relay';

import {
  PanelContainer,
  Panel,
  PanelBody,
  Grid,
  Row,
  Col,
  Form,
  FormControl } from '@sketchpixy/rubix';

import Todo from '../components/Todo';
import TodoForm from '../components/TodoForm';

import AddTodoMutation from '../mutations/AddTodoMutation';
import RemoveTodoMutation from '../mutations/RemoveTodoMutation';

class AllTodos extends React.Component {
  static contextTypes = {
    relay: Relay.PropTypes.Environment,
  };

  render() {
    let user = this.props.user;
    let todos = user.todos.edges;

    return (
      <PanelContainer>
        <Panel>
          <PanelBody style={{padding: 0, paddingBottom: 25}}>
            <Grid>
              <Row>
                <Col xs={12}>
                  <h3>Todo List:</h3>

                  <TodoForm relay={this.context.relay} user={user} />

                  {todos.map(({node}) => {
                    return <Todo key={node.id} todo={node} user={user} />;
                  })}
                </Col>
              </Row>
            </Grid>
          </PanelBody>
        </Panel>
      </PanelContainer>
    );
  }
}

const AllTodosContainer = Relay.createContainer(AllTodos, {
  fragments: {
    user: () => Relay.QL`
      fragment on User {
        todos(first: 99999999) {
          edges {
            node {
              id,
              todo,
              completed,
              ${Todo.getFragment('todo')},
            }
          }
        },
        ${AddTodoMutation.getFragment('user')},
        ${RemoveTodoMutation.getFragment('user')}
      }
    `,
  }
})

export default AllTodosContainer;

We first import Todo, TodoForm components and AddTodoMutation, RemoveTodoMutation that we defined in the previous section.

We have also defined an AllTodos component class in the above code. This component renders all todos fetched from the backend as well as the TodoForm.

However, we do not directly export the AllTodos component. We instead create an intermediate Relay container by calling the Relay.createContainer method which also defines fragments that will be part of the final query. We then export the newly created AllTodosContainer as a Route component.

Step 2: Create relay-todolist/src/routes/EditTodo.js

We will now define a page where we can edit a specific Todo item.

Create a file relay-todolist/src/routes/EditTodo.js and paste the following code:


import React from 'react';
import Relay from 'react-relay';
import ReactDOM from 'react-dom';

import { withRouter } from 'react-router';

import {
  Row,
  Col,
  Grid,
  Form,
  Alert,
  Panel,
  Button,
  Checkbox,
  PanelBody,
  FormGroup,
  FormControl,
  ControlLabel,
  PanelContainer } from '@sketchpixy/rubix';

import UpdateTodoMutation from '../mutations/UpdateTodoMutation';

@withRouter
class EditTodoForm extends React.Component {
  static contextTypes = {
    relay: Relay.PropTypes.Environment,
  };

  state = {
    errors: []
  };

  editTodo(e) {
    e.preventDefault();

    let input = ReactDOM.findDOMNode(this.input);
    let todo = input.value;
    let completed = this.checkbox.checked;

    this.context.relay.commitUpdate(
      new UpdateTodoMutation({
        user: this.props.user,
        todo: this.props.user.getTodo,
        todoText: todo,
        completed: completed
      }), {
      onSuccess: () => {
        this.setState({
          errors: []
        });

        this.props.router.push('/');
      },
      onFailure: (transaction) => {
        let error = transaction.getError();
        let source = error.source;
        let message = error.message;
        if (source) {
          source = source.errors[0].message;

          this.setState({
            errors: source.split('\n')
          });
        } else {
          this.setState({
            errors: [message]
          })
        }
      }
    });
  }

  render() {
    let errors = this.state.errors.length ?
      (
        <Alert danger dismissible>
          {this.state.errors.map((error, i) => {
            return <div key={i}>{error}</div>
          })}
        </Alert>
      ) : null;

    let user = this.props.user;
    let getTodo = user.getTodo;

    let { todo, completed } = getTodo;

    return (
      <div>
        {errors}
        <Form onSubmit={::this.editTodo}>
          <FormGroup controlId='todoText'>
            <ControlLabel>Todo Text</ControlLabel>
            <FormControl type='text' placeholder='A todo item...' defaultValue={todo} ref={(input) => this.input = input} autoFocus />
          </FormGroup>
          <FormGroup controlId='todoComplete'>
            <Checkbox inputRef={(checkbox) => { this.checkbox = checkbox; }} defaultChecked={completed} >
              Mark as Completed
            </Checkbox>
          </FormGroup>
          <FormGroup>
            <Button type='submit' bsStyle='blue' onlyOnHover>Update Todo</Button>
          </FormGroup>
        </Form>
      </div>
    );
  }
}

class EditTodo extends React.Component {
  static contextTypes = {
    relay: Relay.PropTypes.Environment,
  };

  render() {
    return (
      <PanelContainer>
        <Panel>
          <PanelBody style={{padding: 0, paddingBottom: 25}}>
            <Grid>
              <Row>
                <Col xs={12}>
                  <h3>Editing Todo Item:</h3>
                  <EditTodoForm relay={this.context.relay} user={this.props.user} />
                </Col>
              </Row>
            </Grid>
          </PanelBody>
        </Panel>
      </PanelContainer>
    );
  }
}

const EditTodoContainer = Relay.createContainer(EditTodo, {
  initialVariables: {
    id: null,
  },
  fragments: {
    user: () => Relay.QL`
      fragment on User {
        getTodo(id: $id) {
          id,
          todo,
          completed,
          ${UpdateTodoMutation.getFragment('todo')},
        }
      }
    `,
  }
});

export default EditTodoContainer;

The above code renders a form wherein you can edit your existing todo item's text and change it's completion state. The form is rendered in the EditTodoForm component which when submitted calls the editTodo method. This method dispatches a mutation called new UpdateTodoMutation({ user, todo, todoText, completed }). If the update is successful, the page is redirected back to the homepage else errors are rendered to the same page.

However, like in the case of AllTodos component, we do not directly export the EditTodo component. We instead create an intermediate Relay container by calling the Relay.createContainer method which also defines fragments that will be part of the final query. We then export the newly created EditTodoContainer as a Route component. One interesting thing to note here is that we define a initialVariables: { id: null } as a property for EditTodoContainer. The id variable is pre-filled by React Router when the user navigates to /todo/edit/:id by mapping :id to id. With the id known, a getTodo(id: $id) query is executed in the backend GraphQL service and the corresponding todo is returned.

Now that we have created our pages, let us define client-side routes to these pages.