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

61
Missing Pages: ReactJS/Flux /GraphQL/RelayJS Khor, @neth_6, re:Culture

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

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

Missing Pages: ReactJS/Flux/GraphQL/RelayJS

Khor, @neth_6, re:Culture

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

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

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

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

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

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

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

GraphQL: Sans RelayJS

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

GraphQL

I speak GraphQL

API Endpoint

Single Endpoint can Deliver all data

store(email: “[email protected]”) { 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 }, … ], ... }, … ],}

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

GraphQL (cont.)

API Endpoint

query { store(email: "[email protected]") { name, address }}

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

GraphQL (cont.)

API Endpoint

query { store(email: "[email protected]") { name, address }}

store(email: “[email protected]”) { name: ‘Hello Shop’, address: ‘1-3-1 Aoyama’}

Welcome to Hello Shop

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

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

GraphQL (cont.)

API Endpoint

query { store(email: "[email protected]") { categories { name, products { name, price, stock } } }}

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

Hello Shop

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

GraphQL (cont.)

API Endpoint

query { store(email: "[email protected]") { 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

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

GraphQL: Setup

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

GraphQL: Like all Client-Server

Browser

http(s)

Any Server

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

GraphQL: Over HTTP(S)

Browser

GraphQLServer

Bundled JS

GraphQLover

http(s), etc.

Any Server

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

GraphQL Over http(s)

GraphQL over http

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

GraphQL: Enabling the Server

Browser

GraphQLServer

Bundled JS

GraphQLover

http(s), etc.

Any Server

Server Libraries

graphqlGraphQL Schema in Hash

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

GraphQL: JS Code

Browser

GraphQLServer

Bundled JS

Bundled JS

Any Server

GraphQLover

http(s), etc.

Server Libraries

graphqlGraphQL Schema in Hash

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

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

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

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

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

ReactJS (Review)

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

ReactJS

● Single-Page Application (SPA)

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

Hello Shop

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

ReactJS (cont.)

● Single-Page Application (SPA)● Cascading Views

Hello Shop

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

ReactJS (cont.)

● Single-Page Application (SPA)● Cascading Views

Hello Shop

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

React (cont.)

● Single-Page Application (SPA)● Cascading Views

Hello Shop

Hierarchical Views => GraphQL Hierarchical Data

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

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

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

React (cont.)

● Single-Page Application (SPA)● Cascading Views

Fetch Data

Hello Shop

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

React (cont.)

● Single-Page Application (SPA)● Cascading Views

Hello Shop

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

React (cont.)

● Single-Page Application (SPA)● Cascading Views

Hello Shop

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

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

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

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} /> ) }

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

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

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

GraphQL

I speak GraphQL

API Endpoint

Single Endpoint can Deliver all data

store(email: “[email protected]”) { 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 }, … ], ... }, … ],}

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

Sample App: Refresh your Memory

Hello Shop

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

Sample App: Simplified

Hello Shop

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

RelayJS: Component & Data Co-locationstore(email: “[email protected]”) { 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

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

store(email: “[email protected]”) { 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

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

store(email: “[email protected]”) { 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

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

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

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

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} /> ) }

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

RelayJS: What it Brings to GraphQL

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

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

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

RelayJS: Setup

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

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

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

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

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

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

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

Flux: The ‘pure’ version

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

Todo App

New Todo

Create

Todo #1

Todo #2Created Todo List

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

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’ ]}

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

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’ ]}

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

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()

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

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()

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

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); } }}

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

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); } }}

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

Flux

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

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

Flux: Additional Slides

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

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);

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

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);

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

Action Creator Trigger<form>

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

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

</form>

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

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

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

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

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

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

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

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