Tokyo React.js #3: Missing Pages: ReactJS/Flux/GraphQL/RelayJS

Preview:

Citation preview

Missing Pages: ReactJS/Flux/GraphQL/RelayJS

Khor, @neth_6, re:Culture

Shed light on assumptions/details glossed over in FB’s docs

Agenda● Using pure Flux● GraphQL

○ Sans RelayJS○ Setup GraphQL on non-NodeJS servers

● RelayJS○ Revisiting ReactJS: Reduce coupling, increase reusability○ What RelayJS Brings to GraphQL○ Setup RelayJS/GraphQL on non-NodeJS servers

React Family: In a Few Words ...● ReactJS: UI data & rendering● Flux: Data flow & code organization● GraphQL: Single API endpoint data retrieval ● RelayJS: React component data declaration & co-location

GraphQL: Sans RelayJS

GraphQL

I speak GraphQL

API Endpoint

Single Endpoint can Deliver all data

store(email: “admin@abc.com”) { name: ‘Hello Shop’, address: ‘1-3-1 Aoyama’ categories: [ { name: ‘Sporting Goods’, products: [ { name: ‘Football’, price: 20, stock: 50 }, { name: ‘Baseball’, price: 5, stock: 30 }, … ], ... }, … ],}

GraphQL (cont.)

API Endpoint

query { store(email: "admin@abc.com") { name, address }}

GraphQL (cont.)

API Endpoint

query { store(email: "admin@abc.com") { name, address }}

store(email: “admin@abc.com”) { name: ‘Hello Shop’, address: ‘1-3-1 Aoyama’}

Welcome to Hello Shop

Visit us at 1-3-1 Aoyama Or shop online

GraphQL (cont.)

API Endpoint

query { store(email: "admin@abc.com") { categories { name, products { name, price, stock } } }}

store { categories: [ { name: ‘Sporting Goods’, products: [ { name: ‘Football’, price:, stock: 50 }, … }, ... ]}

Hello Shop

GraphQL (cont.)

API Endpoint

query { store(email: "admin@abc.com") { categories { name, products { name, price, stock } } }}

store { categories: [ { name: ‘Sporting Goods’, products: [ { name: ‘Football’, price:, stock: 50 }, … }, ... ]}

Single endpoint

Hierarchical data query

Client-specified query

Data in 1 round-trip

GraphQL: Setup

GraphQL: Like all Client-Server

Browser

http(s)

Any Server

GraphQL: Over HTTP(S)

Browser

GraphQLServer

Bundled JS

GraphQLover

http(s), etc.

Any Server

GraphQL Over http(s)

GraphQL over http

GraphQL: Enabling the Server

Browser

GraphQLServer

Bundled JS

GraphQLover

http(s), etc.

Any Server

Server Libraries

graphqlGraphQL Schema in Hash

GraphQL: JS Code

Browser

GraphQLServer

Bundled JS

Bundled JS

Any Server

GraphQLover

http(s), etc.

Server Libraries

graphqlGraphQL Schema in Hash

GraphQL: Required JS Libraries

Browser

GraphQLServer

Bundled JS

Bundled JS

Any Server

JS Librariesreactreact-domgraphql

GraphQLover

http(s), etc.

Server Libraries

graphqlGraphQL Schema in Hash

GraphQL: Bundling Your JS Code

Browser

GraphQLServer

Bundled JS

Bundled JS

Any Server

JS Librariesreactreact-domgraphql

GraphQLover

http(s), etc.

Server Libraries

graphql

Your JS

browserify/webpackGraphQL

Schema in Hash

ReactJS (Review)

ReactJS

● Single-Page Application (SPA)

Courtesy: https://facebook.github.io/react/docs/thinking-in-react.html

Hello Shop

ReactJS (cont.)

● Single-Page Application (SPA)● Cascading Views

Hello Shop

ReactJS (cont.)

● Single-Page Application (SPA)● Cascading Views

Hello Shop

React (cont.)

● Single-Page Application (SPA)● Cascading Views

Hello Shop

Hierarchical Views => GraphQL Hierarchical Data

ReactJS (cont.)Abstraction

Each ReactJS element knows:

● The data it needs● How to render itself with HTML fragments● The data it passes to its children

React (cont.)

● Single-Page Application (SPA)● Cascading Views

Fetch Data

Hello Shop

React (cont.)

● Single-Page Application (SPA)● Cascading Views

Hello Shop

React (cont.)

● Single-Page Application (SPA)● Cascading Views

Hello Shop

Passing Data to Childrenthis.props = { store: name: ‘Hello Shop’ categories: [ { name: 'Sporting Goods', items: [ { name: 'Football', price: … } … ], }, ... ], },}

Use Data & Render this.props.store.name

Pass Downthis.props.store.categories

Not so Loose Coupling, Not so High Reuse ● Parent needs to know about child’s data

○ Need to fetch data for children○ Need to pass correct data to children

render() { return ( <Store>{this.props.store} /> <Categories categories={this.props.store.categories} /> ) }

RelayJS: Component-Data Co-locationReduce coupling, increase reusability

GraphQL

I speak GraphQL

API Endpoint

Single Endpoint can Deliver all data

store(email: “admin@abc.com”) { name: ‘Hello Shop’, address: ‘1-3-1 Aoyama’ categories: [ { name: ‘Sporting Goods’, products: [ { name: ‘Football’, price: 20, stock: 50 }, { name: ‘Baseball’, price: 5, stock: 30 }, … ], ... }, … ],}

Sample App: Refresh your Memory

Hello Shop

Sample App: Simplified

Hello Shop

RelayJS: Component & Data Co-locationstore(email: “admin@abc.com”) { name: ‘Hello Shop’, address: ‘1-3-1 Aoyama’ categories: [ { name: ‘Sporting Goods’, products: [ { name: ‘Football’, price: 20, stock: 50 }, { name: ‘Baseball’, price: 5, stock: 30 }, … ], ... }, … ],}

fragment on Store { name, address}

Hello Shop

store(email: “admin@abc.com”) { name: ‘Hello Shop’, address: ‘1-3-1 Aoyama’ categories: [ { name: ‘Sporting Goods’, products: [ { name: ‘Football’, price: 20, stock: 50 }, { name: ‘Baseball’, price: 5, stock: 30 }, … ], ... }, … ],}

fragment on Store { categories { name, products, }}

RelayJS: Component & Data Co-location

store(email: “admin@abc.com”) { name: ‘Hello Shop’, address: ‘1-3-1 Aoyama’ categories: [ { name: ‘Sporting Goods’, products: [ { name: ‘Football’, price: 20, stock: 50 }, { name: ‘Baseball’, price: 5, stock: 30 }, … ], ... }, … ],}

Hello Shop

RelayJS will fetchUNION of data

Passing Data to Childrenthis.props = { store: name: ‘Hello Shop’ categories: [ { name: 'Sporting Goods', items: [ { name: 'Football', price: … } … ], }, ... ], },}

Use Data & Render this.props.store.name

Pass Downthis.props.store.categories

Not so Loose Coupling, Not so High Reuse ● Parent needs to need NOT know about child’s data

○ Need to fetch data for children○ Need to pass correct data to children

render() { return ( <Store>{this.props.store} /> <Categories categories={this.props.store.categories} /> ) }

RelayJS: What it Brings to GraphQL

Why RelayJS?● Usable features:

○ Component-Data Co-location○ Connection Id: Data re-fetching○ Connections: One-to-Many Relationships/Pagination○ Mutations: Modified data auto-updates affected React components

● Implicit features:○ Auto-fetch declared data (no AJAX code)○ Caching, batching data

● Bells & Whistles:○ Show spinner, etc. during loading○ Show error message, etc., if data fetch fail○ Optimistic UI updates

RelayJS: Setup

RelayJS: Component-Data Co-location

Browser

GraphQL/RelayJSServer

Bundled JSAny Server

JS Librariesreactreact-dom

react-relay

babelify-relay-plugin

babelify

RelayJS containers

calling GraphQL

overhttp(s), etc.

graphql

Server Libraries

graphql

Your JS with

Relay.QL

browserify/webpack

GraphQL Schema in JSON

Bundled JS

GraphQL Schema in Hash

Converter

graphql-relay

References● Articles

○ GraphQL/RelayJS (non NodeJS): https://medium.com/@khor/relay-facebook-on-rails-8b4af2057152○ Pure ‘Flux’ (non NodeJS): https://medium.com/@khor/back-to-front-rails-to-facebook-s-flux-ae815f81b16c

● Starter-kit○ Rails: https://github.com/nethsix/relay-on-rails

● Choices: React, React (with Container), Flux/Redux, GraphQL/RelayJS ○ Shared by @koba04 - http://andrewhfarmer.com/react-ajax-best-practices/

● Follow: @neth_6, @reculture_us

Thank you:● All for coming!● Toru for invite!● Facebook for tech & engineers!

Flux: The ‘pure’ version

Todo App

New Todo

Create

Todo #1

Todo #2Created Todo List

Todo App: React with AJAX Render

render() { return (_.map( this.state.todos, (e) => { return <div>{e}</div> }) )}

Get Todos

componentDidMount() { $.ajax { url: …GET success: (data) => { this.setState(data); } }}

Add Todo

onAddTodo() { $.ajax { url: …POST success: (data) => { this.setState(data); } }}

Edit Todo

onEditTodo() { $.ajax { url: …PUT success: (data) => { this.setState(data); } }}

. . .

Data

this.setState = { todos: [ ‘Todo #1’, ‘Todo #2’ ]}

Flux: Code Organization

Views

Actions

Get TodosgetTodot() { $.ajax { url: …GET success: (data) => { this.setState(data); } }}

Add TodoaddToto() { $.ajax { url: …POST success: (data) => { this.setState(data); } }}

Edit TodoeditTodo() { $.ajax { url: …PUT success: (data) => { this.setState(data); } }}

this.setState = { todos: [ ‘Todo #1’, ‘Todo #2’ ]}

Flux: Data Flow

Views

Actions

Get TodosgetTodos() { $.ajax { url: …GET success: (data) => { this.setState(data); } }}

Add TodoaddTodo() { $.ajax { url: …POST success: (data) => { this.setState(data); } }}

Edit TodoeditTodo() { $.ajax { url: …PUT success: (data) => { this.setState(data); } }}

this.setState = { todos: [ ‘Todo #1’, ‘Todo #2’ ]}

Dispatcher

● Throttles one Action at a time● waitsFor()

Get TodosgetTodot() { $.ajax { url: …GET success: (data) => { this.setState(data); } }}

Add TodoaddToto() { $.ajax { url: …POST success: (data) => { this.setState(data); } }}

Edit TodoeditTodo() { $.ajax { url: …PUT success: (data) => { this.setState(data); } }}

Flux: Data Flow

Views

this.setState = { todos: [ ‘Todo #1’, ‘Todo #2’ ] users: [ ‘User #1, ‘User #2’ ]}

Actions

Views

Get UsersgetUsers() { $.ajax { url: …GET success: (data) => { this.setState(data); } }}

Add UseraddUser() { $.ajax { url: …POST success: (data) => { this.setState(data); } }}

Edit UsereditUser() { $.ajax { url: …PUT success: (data) => { this.setState(data); } }}

Dispatcher

● Throttles one Action at a time● waitsFor()

Flux: Data Flow

Views

Actions

Views

Dispatcher

● Throttles one Action at a time● waitsFor()

Todo Store

User Store

register

this.setState = { todos: [ ‘Todo #1’, ‘Todo #2’ ]} this.setState = {

users: [ ‘User #1, ‘User #2’ ]}

register

Get TodosgetTodot() { $.ajax { url: …GET success: (data) => { this.setState(data); } }}

Add TodoaddToto() { $.ajax { url: …POST success: (data) => { this.setState(data); } }}

Edit TodoeditTodo() { $.ajax { url: …PUT success: (data) => { this.setState(data); } }}

Get UsersgetUsers() { $.ajax { url: …GET success: (data) => { this.setState(data); } }}

Add UseraddUser() { $.ajax { url: …POST success: (data) => { this.setState(data); } }}

Edit UsereditUser() { $.ajax { url: …PUT success: (data) => { this.setState(data); } }}

Flux: Data Flow

Views

Actions

Views

Dispatcher

● Throttles one Action at a time● waitsFor()

Todo Store

User Store

listen

this.setState = { todos: [ ‘Todo #1’, ‘Todo #2’ ]} this.setState = {

users: [ ‘User #1, ‘User #2’ ]}

listen

listenTodo Actions

listenUser Actions

Get TodosgetTodot() { $.ajax { url: …GET success: (data) => { this.setState(data); } }}

Add TodoaddToto() { $.ajax { url: …POST success: (data) => { this.setState(data); } }}

Edit TodoeditTodo() { $.ajax { url: …PUT success: (data) => { this.setState(data); } }}

Get UsersgetUsers() { $.ajax { url: …GET success: (data) => { this.setState(data); } }}

Add UseraddUser() { $.ajax { url: …POST success: (data) => { this.setState(data); } }}

Edit UsereditUser() { $.ajax { url: …PUT success: (data) => { this.setState(data); } }}

Flux

Courtesy: http://fluxxor.com/what-is-flux.html

Flux: Additional Slides

Todo App: React with props Data

const _props = { todos: [ ‘Todo #1’, ‘Todo #2’ ]}

Code

render() { return (_.map( this.props.todos, (e) => { return <div>{e}</div> }) )}

Initialize

const _root = document.findElementById(‘root’);ReactDOM.render(<TodoApp {..._props} />, _root);

Todo App with FluxData

const _props = { todos: [ ‘Todo #1’, ‘Todo #2’ ]}

Code

render() { return (_.map( this.props.todos, (e) => { return <div>{e}</div> }) )}

Initialize

const _root = document.findElementById(‘root’);ReactDOM.render(<TodoApp {..._props} />, _root);

Action Creator Trigger<form>

<input id=’todo-text’ type=’text’ />

<button onClick=TodoActions.create($(‘#todo-text’).val())>Create</button>

</form>

Action CreatorTodoActions: { create: function(text) { // Take some action, e.g., call REST API AppDispatcher.dispatch({ actionType: TodoConstants.TODO_CREATE, // Basically ‘create’ text: text }); }, ….}

StoreAppDispatcher.register(function(action) { // action is passed in by Action Creatorvar event = action.event; switch(action.actionType) { case TodoConstants.TODO_CREATE: // Do whatever, e.g., update local store data or fetch fresh data from server TodoStore.emitChange(); break; …. }}

register

Store (cont.)var TodoStore = assign({}, EventEmitter.prototype, { // EventEmitter provides emit, on, removeListener, etc. methods addChangeListener: function(callback) { this.on(CHANGE_EVENT, callback); }, removeChangeListener: function(callback) { this.removeListener(CHANGE_EVENT, callback); }, emitChange: function() { this.emit(CHANGE_EVENT); }, ...}

register

Controller-View// This is where React is usedvar TodoApp = React.createClass({ componentDidMount: function() { TodoStore.addChangeListener(this._onChange); }, componentWillUnmount: function() { TodoStore.removeChangeListener(this._onChange); }, _onChange: function() { this.setState(TodoStore.getData()); }, ...}

register