Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rails to React

Avatar for Jordan Ell Jordan Ell
December 17, 2015

Rails to React

How I learned to Stop Worrying and Love Webpack, Reflux, and Node

Avatar for Jordan Ell

Jordan Ell

December 17, 2015
Tweet

Other Decks in Programming

Transcript

  1. Rails to React or: How I learned to Stop Worrying

    and Love Webpack, Reflux, and Node
  2. Dynamic Content On Cached Pages No API Ruby hash into

    JSON into cookie string into JSON into content. Ran into header size limits with some authors. Problem only gets worse over time. Lets rebuild the architecture with an API and a React front end.
  3. Current Choices for React + Rails Using the asset pipeline:

    react-rails gem <%= react_component(User, name: 'John') %> <!-- becomes: --> <div data-react-class="User" data-react-props="{&quot;name&quot;:&quot;John&quot;}"></div> On page load, the react_ujs driver will scan the page and mount components var nodes = window.ReactRailsUJS.findDOMNodes(searchSelector); for (var i = 0; i < nodes.length; ++i) { var node = nodes[i]; var constructor = window[className]; // Some variables omitted renderer.render(React.createElement(constructor, props), node); }
  4. Things We Didn’t Like • Uses the asset pipeline •

    Uses the global namespace • No real support for Flux (for server side rendering especially) • No native client side routing
  5. Shiny New Things We Do Like • Webpack • Reflux

    • React Router • ES6 Let build a completely custom stack and integrate it with Rails later.
  6. Webpack - Loaders module.exports = function(content) { return someSyncOperation(content); };

    module.exports.raw = true; Load anything you want Enables us to do things like: require('styles/common/footer.scss'); var logo = require('assets/logo.png');
  7. Webpack - Entry Points • Multiple bundles for multiple parts

    of the app • Common bundles { entry: { bookstore: "./bookstore.js", user_dashboard: "./user_dashboard.js", author_dashboard: "./author_dashboard.js" }, output: { path: path.join(__dirname, "dist"), filename: "[name].entry.js" } } Rails knows which route is being hit, so it can handle sending the correct entry file.
  8. Reflux It breaks the rules: • Every action has it’s

    own dispatcher • Actions are listenable, stores and components can listen • Stores can listen to other stores
  9. Reflux - Actions import Reflux from 'reflux'; import Communicator from

    'communicator.js'; let BookActions = Reflux.createActions({ 'getBook': { children: ['completed', 'failure'] } }); BookActions.getBook.listen((bookId, params = {}) => { Communicator.get(`books/${ bookId }`, params) .then(Communicator.checkErrors) .then(response => response.json()) .then((responseJSON) => { BookActions.getBook.completed(responseJSON); }) .catch((error) => { BookActions.getBook.failure(error) }) });
  10. Reflux - Stores import Reflux from 'reflux'; import BookActions from

    '../actions/book.js'; const BooksStore = Reflux.createStore({ listenables: BookActions, init() { this.data = { books: {} }; }, onGetBookCompleted(book) { this.data.books[book.slug] = book this.trigger(this.data) } }); export default BooksStore
  11. React Router import React from 'react' import Router from 'react-router';

    import App from '../components/app.jsx'; import BookShow from '../components/book/show.jsx'; import Home from '../components/home.jsx'; const { Route } = Router; const routes = ( <Route handler={ App }> <Route path="/" handler={ Home } /> <Route path="/:id" handler={ BookShow } /> </Route> ) export default routes;
  12. React Router import React from 'react'; import ReactDOM from 'react-dom';

    import Router from 'react-router'; import routes from './routes/index.jsx'; // Boot the react app Router.run(routes, Router.HistoryLocation, (Root) => { var root = $('#react-root')[0]; ReactDOM.render(<Root />, root); });
  13. Server Side Rendering • Can Google crawl dynamic pages? •

    Page load speed ◦ Avoid hitting API for same content • Render everything that doesn’t depend on current user • Prepopulate reflux stores with data from Rails • Cache result on server
  14. Server Side Rendering - Asset Pipeline Loading everything through the

    asset pipeline lets you write JS in Rails js_code = <<-JS (function () { var result = ReactDOMServer.#{render_function}(React.createElement(#{component_name}, #{props})); return result; })() JS Rails does not play nice with Webpack
  15. Server Side Rendering - Webpack import React from 'react'; import

    ReactDOMServer from 'react-dom/server'; import Router from 'react-router'; import routes from './routes/index.jsx'; var rendered = ''; Router.run(routes, path, (Root) => { rendered = ReactDOMServer.renderToString(<Root />); }); console.log(rendered); • Lifecycle methods do not trigger • Needs a path • Needs prepopulated data
  16. Server Side Rendering - Rails featured_book = Book.featured_book booksStore =

    {} add_object_to_store(featured_book, BookSerializer, booksStore) @reflux_data = { 'books': booksStore }.to_json Convert Ruby hash to JSON and prepare a fake Reflux store
  17. Server Side Rendering - Node def render_react_root script = Rails.root.join('public',

    'assets', 'server-bundle.js') data = @fluxxor_data.to_s output = sh("node #{script} -p / -s '#{data}'") "<div id='react-root'>#{output}</div>".html_safe end Run the webpack bundle in node with our data as parameters
  18. Server Side Rendering - Node import BooksStore from './stores/books.js'; import

    _ from 'lodash'; // Prepopulate all the stores for (let key of Object.keys(storeDataRaw)) { let store = null; switch(key) { case 'books': store = BooksStore; break; } if (store) { store.data = _.merge(store.data, storeDataRaw[key]); } } Add data from node arguments into stores before we render
  19. Server Side Rendering - Leftovers • Have a strict rules

    about writing your components ◦ Always check if data is in the store before requesting it from the server ◦ Only request new data after componentDidMount ◦ Be able to render without references to window / document • Let Rails perform normal page caching • On page load, boot react app as normal
  20. Results • Rails API and page caching • React front

    end • Reflux for client side data storage and manipulation • Webpack bundler • Node for server side rendering • Not dependant on rails • Not dependant on any Flux implementation • No asset pipeline • No namespacing issues