View
2.773
Download
1
Category
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
Recommended