Daniel Doubrovkine bio photo

Daniel Doubrovkine

aka dB., CTO at artsy.net, fun at playplay.io, NYC

Email Twitter LinkedIn Github Strava

In the previous post I added a React Native ticking timer. I now have enough client-side parts and it’s time to wire up the client app to a server. This cookbook should be helpful to anyone doing it for the first time.

Rails Server

Boilerplate

Make a Rails API server with MongoDB.

  1. Create a server with rails new server --skip-active-record -T --api, 33-minutes-server@74fd5a.
  2. Generate a MongoDB config with rails g mongoid:config, 33-minutes-server@3e1c6f.
  3. Add RuboCop, my workflow typically consists of rubocop -a ; rubocop --auto-gen-config, 33-minutes-server@fc034c.
  4. Add RSpec, 33-minutes-server@3af9b9.
  5. Enable Travis-CI, 33-minutes-server@8050ab.

GraphQL Controller

Follow my tutorial on exposing a GraphQL API and use warden to keep authenticated user context. Since Warden requires cookies, re-add the ActionDispatch::Cookies middleware to config/application.rb.

config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore, key: '_namespace_key'

A GraphQL mutation creates a user.

Mutations::CreateUserMutation = GraphQL::Relay::Mutation.define do
  name 'createUser'
  input_field :email, !types.String
  input_field :password, !types.String
  input_field :name, !types.String
  return_field :user, Types::UserType
  resolve ->(_object, inputs, ctx) {
    user = User.create!(
      email: inputs[:email],
      name: inputs[:name],
      password: inputs[:password]
    )
    ctx[:warden].set_user(user)
  { user: user }
}
end

Similar GraphQL mutations login and logout users.

When serializing users with Warden, don’t serialize the whole object or you’ll get a ActionDispatch::Cookies::CookieOverflow exception.

Rails.application.config.middleware.insert_after Rack::ETag, Warden::Manager do |manager|
  manager.failure_app = GraphqlController
  Warden::Manager.serialize_into_session do |user|
    user.id
  end
  Warden::Manager.serialize_from_session do |id|
    User.find(id)
  end
end

You can see the entire code in 33-minutes-server@52a249 and 33-minutes-server@0c3c4d.

Schema IDL

Run rake graphql:schema:idl to generate a schema.graphql file and copy it to the client app’s schema folder. This will have to be done every time the Rails API GraphQL schema changes.

React Native Client

Signed In vs. Signed Out

I used a switch navigator to toggle between the signed-in and signed-out states in 33-minutes-app@9f252c. Each state is its own stack navigator.

const SignedOut = createStackNavigator({
  SignIn: {
    screen: SignIn
  },
  SignUp: {
    screen: SignUp
  }
});

const SignedIn = createStackNavigator({
  Main: {
    screen: Tabs
  },
  Settings: {
    screen: Settings
  }
});

export const createRootNavigator = (signedIn = false) => {
  return createSwitchNavigator(
    {
      SignedIn: {
        screen: SignedIn
      },
      SignedOut: {
        screen: SignedOut
      }
    },
    {
      initialRouteName: signedIn ? 'SignedIn' : 'SignedOut'
    }
  )
}

The entire app is that navigator.

export default class App extends Component {
  render() {
    const Layout = createRootNavigator();
    return (
      <Layout />
    );
  }
}

To toggle between the states, navigate to either SignedIn or SignedOut (see the mutations below).

Relay Boilerplate

The Relay Quick Start Guide is pretty good. We throw in react-relay with yarn add react-relay and relay-compiler with yarn add -dev relay-compiler. Add a relay script into package.json.

"scripts": {
  "relay": "relay-compiler --src ./ --schema ./schema/schema.graphql --extensions=js --extensions=jsx"
}

Run yarn relay. Generally you will be running yarn relay --watch, which generates JavaScript code under a __generated__ folders every time something changes. You will also have to restart this process when schema changes. This is not integrated with the rest of the compiler toolchain because that would be too easy.

Environment

Copy-paste a relay environment into app/Environment.js. Note my hard-coded http://localhost:3000/graphql for the Rails server. I will make this dynamic in the future.

import { Environment, Network, RecordSource, Store } from 'relay-runtime';

function fetchQuery(operation, variables) {
  return fetch('http://localhost:3000/graphql', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      query: operation.text,
      variables,
    }),
  }).then(response => {
    return response.json();
  });
}

const environment = new Environment({
  network: Network.create(fetchQuery),
  store: new Store(new RecordSource()),
});

export default environment;

Create User Mutation

Copy-paste a relay-style mutation into app/mutations/CreateUserMutation.js.

import { graphql } from 'react-relay'
import commitMutation from 'relay-commit-mutation-promise'

const mutation = graphql`
  mutation CreateUserMutation($input: createUserInput!) {
    createUser(input: $input) {
      user {
        id
      }
    }
  }
`

function commit({ environment, input }) {
  const variables = { input }
  return commitMutation(environment, {
    mutation,
    variables
  })
}

export default {
  commit
}

Create a User

Invoke the mutation, switch to a signed-in navigator upon success, or set an error message otherwise.

CreateUserMutation.commit({
  environment,
  input: {
    name: this.state.name,
    email: this.state.email,
    password: this.state.password
  }
}).then(response => {
  this.props.navigation.navigate('SignedIn')
}).catch(error => {
  this.setState({ message: error.message });
});

Login is very similar. For now we’re using cookies to store a session and don’t remember anything on the client, which means that reloading the app logs the user out.

Show Some Data

To show data wrap up a GraphQL query into a QueryRenderer. You’ll always need one.

render() {
  return (
    <QueryRenderer
      environment={this.props.relay.environment}
      query={graphql`
        query UserQuery {
         user {
          id
          name
         }
        }
      `}

      render={({error, props, retry}) => {
        if (error) {
          // todo
        }

        if (! props) {
          return <Text>Loading ...</Text>
        }

        // props.user ...
      }
    />
  )
}

Code

See 33-minutes-app@e3e6b9 for complete code. In the next post I’ll enable Relay-style pagination.