Skip to content

Instantly share code, notes, and snippets.

@kibs
Last active March 14, 2019 08:11
Show Gist options
  • Save kibs/80fd7475ada1b376fcb36b7b9f02ebb6 to your computer and use it in GitHub Desktop.
Save kibs/80fd7475ada1b376fcb36b7b9f02ebb6 to your computer and use it in GitHub Desktop.
Centic - Rails/React/GraphQL

Step 1 - Rails

rails new app
cd app
bin/rails server

Step 2 - Webpacker

https://github.com/rails/webpacker

echo "gem 'webpacker'" >> Gemfile
bundle install

rails webpacker:install
# above fails on my machine, below does the same
# bin/rails app:template LOCATION=/Users/sune/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/webpacker-4.0.2/lib/install/template.rb

rails webpacker:install:react
# above fails on my machine, below does the same
# bin/rails app:template LOCATION=/Users/sune/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/webpacker-4.0.2/lib/install/react.rb

yarn upgrade

bin/rails server
bin/webpack-dev-server

application.html.erb

<%= javascript_pack_tag 'application' %>
<%= stylesheet_pack_tag 'application' %>

Step 3 - React

https://github.com/reactjs/react-rails

echo "gem 'react-rails'" >> Gemfile
bundle install
bin/rails generate react:install
bin/rails server

Step 4 - Rails GraphQL

https://graphql-ruby.org

echo "gem 'graphql'" >> Gemfile
bundle install

bin/rails generate graphql:install
# graphiql added by above
bundle install

bin/rails server

http://localhost:3000/graphiql

Step 5 - React GraphQL

https://www.apollographql.com/docs/react/

yarn add apollo-boost react-apollo graphql
bin/webpack-dev-server

Step 1 - New

bash new.sh
cd demo
bin/rails server

http://localhost:3000/appointments Opret 5 Appointments

Step 2 - Webpacker

bash ../webpacker.sh
bin/rails server
bin/webpack-dev-server

application.html.erb

<%= javascript_pack_tag 'application' %>
<%= stylesheet_pack_tag 'application' %>
  • Demo: DevTools "Hello World from Webpacker"
  • Demo: app/javascript
  • Slet: hello_react.js
  • Nævn: application.css via import fra application.js
  • Nævn: kun entrypoints skal være i packess
  • Genstart webpack-dev-server

Step 3 - React

bash ../react.sh
bin/rails server

Tilføj Appointments component

import React from 'react';

function Appointments() {
  return (
    <div>HELLO REACT WORLD</div>
  )
}

export default Appointments
  • appointments/index.html.erb
<%= react_component("Appointments") %>
  • Vis live edit demo

Appointments data fra rails

  • Lav AppointmentsTable component
import React from 'react';

function AppointmentsTable({ appointments }) {
  return (
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Date</th>
          <th colSpan="3"></th>
        </tr>
      </thead>

      <tbody>
        {appointments.map(({ id, name, date }) => (
          <tr key={id}>
            <td>{name}</td>
            <td>{date}</td>
            <td><a href={`/appointments/${id}`}>Show</a></td>
            <td><a href={`/appointments/${id}/edit`}>Edit</a></td>
            <td><a href={`/appointments/${id}`} data-method="delete" data-confirm="Are you sure?">Destroy</a></td>
          </tr>
        ))}
      </tbody>
    </table>
  )
}

export default AppointmentsTable
  • Brug AppointmentsTable i Appointments
import React from 'react';
import AppointmentsTable from './AppointmentsTable';

function Appointments({ appointments }) {
  return (
    <AppointmentsTable appointments={appointments} />
  )
}

export default Appointments

appointments/index.html.erb

<%= react_component("Appointments", { appointments: @appointments.map(&:as_json) }) %>
  • Demo: Vis kilde i browser
  • Demo: Vis elements i DevTools
  • Demo: Vis React i DevTools

Client Side Filter

  • Appointments component
import React, { useState } from "react"
import AppointmentsTable from './AppointmentsTable';

function Appointments({ appointments }) {
  const [filter, setFilter] = useState("");

  const filtered = (all) => {
    if(!filter.trim()) return all;
    return all.filter(a => a.name.includes(filter));
  }

  return (
    <React.Fragtment>
      <div>
        <label>Filter</label>
        <input value={filter} onChange={e => setFilter(e.target.value)} />
      </div>
      <AppointmentsTable appointments={filtered(appointments)} />
    </React.Fragment>
  );
}

export default Appointments

Step 4 - Rails GraphQL

bash ../graphql.sh
bin/rails server
bin/rails g graphql:object Appointment id:ID name:String date:String
  • Fix null: false + types i AppointmentType
  • Fjern Mutation fra demo_schema
  • Fjern testfield fra query_type
  • Tilføj appointments til query_type
module Types
  class QueryType < Types::BaseObject
    field :appointments, [Types::AppointmentType], null: false

    def appointments
      Appointment.all
    end
  end
end

Step 5 - React GraphQL

yarn add apollo-boost react-apollo graphql
bin/webpack-dev-server
  • Fjern appointments data fra rails view appointments/index.html.erb
  • Setup component
import React from 'react';
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "react-apollo";

const client = new ApolloClient();

function Setup({ children }) {
  return (
    <ApolloProvider client={client}>
      {children}
    </ApolloProvider>
  )
}

export default Setup;
  • Tilpas Appointments uden Query
import React, { useState } from "react"

import Setup from './Setup';
import AppointmentsTable from './AppointmentsTable';

function Appointments() {
  const [filter, setFilter] = useState("");

  const filtered = (all) => {
    if(!filter.trim()) return all;
    return all.filter(a => a.name.includes(filter));
  }

  return (
    <Setup>
      <div>
        <label>Filter</label>
        <input value={filter} onChange={e => setFilter(e.target.value)} />
      </div>
      <AppointmentsTable appointments={filtered([])} />
    </Setup>
  );
}

export default Appointments
import React, { useState } from "react"
import { Query } from "react-apollo";
import gql from "graphql-tag";

import Setup from './Setup';
import AppointmentsTable from './AppointmentsTable';

const QUERY = gql`
  query Appointments {
    appointments {
      id
      name
      date
    }
  }
`

function Appointments() {
  const [filter, setFilter] = useState("");

  const filtered = (all) => {
    if(!filter.trim()) return all;
    return all.filter(a => a.name.includes(filter));
  }

  return (
    <Setup>
      <div>
        <label>Filter</label>
        <input value={filter} onChange={e => setFilter(e.target.value)} />
      </div>
      <Query query={QUERY}>
        {({ data, error, loading}) => {
          console.log({ data, error, loading });
          return (
            <AppointmentsTable appointments={[]} />
          )
        }}
      </Query>
    </Setup>
  );
}

export default Appointments
  • Fix InvalidToken Error i ApplicationController
protect_from_forgery with: :null_session
  • Demo DevTools evt. med Slow 3G
  • Korrekt loading, error og data
<Query query={QUERY}>
  {({ data, error, loading}) => {
    if(loading) {
      return (
        <div>Loading appointments...</div>
      );
    }

    if(error) {
      console.log(error);
      return (
        <div>Could not load appointments</div>
      );
    }

    return (
      <AppointmentsTable appointments={filtered(data.appointments)} />
    )
  }}
</Query>
  • Server Side Filter
module Types
  class QueryType < Types::BaseObject
    field :appointments, [Types::AppointmentType], null: false do
      argument :filter, String, required: false
    end

    def appointments(filter: nil)
      if filter.empty?
        Appointment.all
      else
        Appointment.where("name LIKE ?", "%#{filter.gsub(" ", "%")}%")
      end
    end
  end
end
import React, { useState } from "react"
import { Query } from "react-apollo";
import gql from "graphql-tag";

import Setup from './Setup';
import AppointmentsTable from './AppointmentsTable';

const QUERY = gql`
  query Appointments($filter: String) {
    appointments(filter: $filter) {
      id
      name
      date
    }
  }
`

function Appointments() {
  const [filter, setFilter] = useState("");

  return (
    <Setup>
      <div>
        <label>Filter</label>
        <input value={filter} onChange={e => setFilter(e.target.value)} />
      </div>
      <Query query={QUERY} variables={{ filter }}>
        {({ data, error, loading}) => {
          if(loading) {
            return (
              <div>Loading appointments...</div>
            );
          }

          if(error) {
            console.log(error);
            return (
              <div>Could not load appointments</div>
            );
          }

          return (
            <AppointmentsTable appointments={data.appointments} />
          )
        }}
      </Query>
    </Setup>
  );
}

export default Appointments
rails new demo --skip-turbolinks --skip-spring --skip-git
cd demo
sed -i '' '/sqlite/d' ./Gemfile
echo "gem 'sqlite3', '~> 1.3', '< 1.4'" >> Gemfile
bundle install
bin/rails generate scaffold Appointment name:string date:date
bin/rails db:migrate
echo "gem 'webpacker'" >> Gemfile
bundle install
# rails webpacker:install
bin/rails app:template LOCATION=/Users/sune/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/webpacker-4.0.2/lib/install/template.rb
# rails webpacker:install:react
bin/rails app:template LOCATION=/Users/sune/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/webpacker-4.0.2/lib/install/react.rb
yarn upgrade
echo "gem 'react-rails'" >> Gemfile
bundle install
bin/rails generate react:install
echo "gem 'graphql'" >> Gemfile
bundle install
bin/rails generate graphql:install
bundle install
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment