Component-Based Architecture
React.js on Rails
Summarized using AI

React.js on Rails

by Michael Chan

In this session at RailsConf 2015, Michael Chan presents 'React.js on Rails,' emphasizing the challenges and advantages of integrating React with Rails applications. The talk begins with an acknowledgment of JavaScript's historically problematic role within the Rails ecosystem, highlighting the complications of managing JavaScript libraries and frameworks.

Chan describes his early experiences with JavaScript in the context of his application, Services, which led to significant learning from the use of frameworks like Batman JS. Key issues included the cumbersome management of separate client-side and server-side application architectures.

To articulate the synergy between Rails and React, Chan presents several key points:

  • Integration Challenges: The transition to modern JavaScript frameworks like React can be rocky, with developers facing issues in state management and application structure when splitting front-end and back-end systems.
  • Adopting React: He emphasizes React's suitability in creating user interfaces that remain aligned with Rails principles — allowing developers to handle components effectively without fully abandoning the Rails stack.
  • Real-Time Applications: Chan describes how React supports sophisticated real-time interactions, illustrated through the use of tools like Pusher in building a real-time commenting feature.
  • Component-Based Architecture: He delves into the fundamental attributes of React components, covering their three main functions: rendering, receiving props, and maintaining state. He relates this to Rails partials to bridge the gap for Ruby developers.
  • Efficient Updates: React’s methodology optimizes the efficiency of updating components and managing state, using techniques like ’setState’ and lifecycle methods to control data flow.
  • Practical Implementations: The tutorial aspect of the talk offers hands-on instructions for creating a React-powered application within a Rails environment, illustrating concepts clearly with code examples.
  • Utilization of React-Rails Gem: He recommends the react-rails gem for smooth integration, mentioning its compatibility with TurboLinks and CoffeeScript as beneficial for developers looking to maximize efficiency while minimizing complexity.
  • Future Considerations: Conclusively, he advises attendees to embrace newer standards like ES6 and JSX, countering the previous hesitations towards these technologies to leverage React’s full potential.

Through his talk, Chan motivates developers to adopt a thoughtful approach to integrating React within Rails, prioritizing simplicity and efficiency while embracing modern JavaScript practices. The main takeaway emphasizes limiting JavaScript usage to essentials, enhancing Rails UIs with React components intelligently without overwhelming the application.

00:00:11.840 All right, let's get this thing started! How the heck is everyone? I'm going to need more than that. Come on, how the heck is everybody? Thank you! Thank you, that makes me feel more comfortable. Thank you so much for coming to this humble little talk. I'm really glad that of all the talks out there, you decided to come to this one. I'm feeling the feels right now.
00:00:29.119 In this talk, we're going to discuss JavaScript. I feel like I've won the lottery being able to talk about JavaScript at a Ruby conference. It's interesting because JavaScript is one of those pieces of the Rails stack that's still pretty terrible in general, and we have an opportunity to talk about how to make it a little bit better.
00:00:41.360 It's interesting because, at this particular conference, there has actually been a lot of talk about JavaScript, including discussions like prepper backpacks and the zombie apocalypse. You might wonder whether JavaScript should be in your zombie apocalypse prepper backpack. So, I want to share how we approach solving the JavaScript problem.
00:01:02.600 My name is Michael Chan, and I go by Chantastic on Twitter. There's a much younger, happier version of me you might find there. I work on an application called Services, which was launched in 2006 using Rails 1.13. Services is an application for churches that helps organize volunteers, manage timelines, and all that kind of cool stuff. Believe it or not, there is actually a market for church software.
00:01:30.000 Since it started in 2006, our approach to JavaScript has been somewhat haphazard. Initially, we just wrote it, but as you know, after some time, sprinkles tend to turn into mountains, and we knew we needed a better structure for our JavaScript applications. In 2012, while developing our second app, we began to examine the landscape and see what others were doing.
00:02:15.560 We looked at larger Rails shops, including Shopify, which was just starting to work on a project called Batman JS. Has anyone here used Batman JS? Good for you! It was quite an interesting experience. Batman had all the markers of a 2012 JavaScript framework, like two-way binding and a plethora of observers for tracking state and updates.
00:02:39.879 We built our application in two pieces: the API on one side and the client-side app that customers actually interacted with. This created a whole new set of problems. Suddenly, we had two sets of controllers, two sets of models, two sets of validations, and two routers. You don't realize how much you enjoy form 4 until you don't have it anymore. It was a terrible situation.
00:03:11.600 Our app felt like two cakes stacked on top of each other. Any change in the front-end app caused a change in the backend app, and it just wasn't good. I asked the product manager for that team if they had any particularly painful scenarios where state was unmanageable in our Batman app. The response was, "Are you kidding? All of them!" He then proceeded to send me about eight cards, but eventually gave up and said, "Just search for cards that have hard refresh in them."
00:03:36.360 This meant we had parts of our app that were so unreliable that the best solution for our JavaScript application was just to say that if a user ever comes to this route, it's probably not going to work; let's just refresh it. As you can imagine, Shopify abandoned Batman JS in 2014, which left us wondering what to do next. They have been vocal against client-side MVC since then.
00:04:07.160 This is Toby, the CEO of Shopify, who discussed how ridiculous client-side JavaScript was. There was a point when he tweeted that the Batman project cost them $100,000, and then it just went up in smoke. As David mentioned in his talk, Batman and Shopify were instrumental in building out TurboLinks 3, which influenced the direction we took.
00:04:32.920 We then rebuilt some of our Batman apps, focusing on maintaining the Rails path. We embraced Rails and started using TurboLinks, and honestly, we really liked it. I know that might be controversial here, but we're pretty into it. Despite this improvement, we still had a lot of JavaScript to write in various parts of our applications.
00:04:58.120 This is from a pull request that was opened, showing how many transient states we had. This wasn't an event that would get sent to the server, responding with server-rendered JavaScript; it was purely for the user. Additionally, we were developing fully real-time pages, like the Services planning tool we have that uses Pusher to connect many devices, including iOS devices.
00:05:42.360 In this real-time application, different users can interact and track how much time has passed. It includes real-time chat features, making it a pretty exciting Javascript MVC's dream scenario. What we needed was something that could scale from these small interactions to these large, fully-realized pages where everything was real-time.
00:06:10.280 We found React to be instrumental in that quest. It allowed us to remain on the Rails path while using minimal JavaScript to get the job done, particularly with the concept of React components. I have two goals for this talk: the first is for you to walk out of here and ship a React feature in an hour. It should take about five minutes to set up, and then you'll have 55 minutes to code.
00:06:45.240 If any of you manage to do that, let me know, and I will gladly give you a digital high five. My second goal is for you to avoid making the same mistakes we did. We made a ton of mistakes—not just in JavaScript but also in porting our application logic to React. I will strive to show you the ideal path we are currently using for all of our new development.
00:07:33.840 Let's do a React primer: React is a JavaScript library developed by Facebook for creating user interfaces using components. Components do three things: they render, they receive props, and they maintain state. You can think of a React component as a partial with locals. For example, if we have an audio service and want to show the songs in an album, this partial should be familiar to anyone who has done Rails for a day.
00:08:07.480 This will render an unordered list of songs; for instance, this particular list could be for the album 1989 by Taylor Swift. So, we're just going to create a new partial file for this and then say render songs, keeping it simple. But if we want this to be reusable, we can't just say "album songs" here; we want to abstract that a bit. Therefore, we'll just say "songs" and require some local data.
00:08:40.880 In our view, we update it to say that "songs" is going to be equal to album songs. So now, if we want to show a list of Taylor Swift albums, we can iterate over the albums, outputting the title each time. Now, when we send the songs in as locals, it still doesn't matter what kind of songs they are. Let's look at how we would do this in JavaScript.
00:09:15.080 Instead of render partial, we use a helper called react component. When working with songs, rather than looking for the songs partial, we would now look for the songs component in the window object. Instead of sending locals in, we send a hash, but it's still the same concept. Now, we just say songs equals album songs. In React, we call these props, short for properties.
00:09:49.360 Props are immutable, and similar to how Yehuda described ownership in Rust yesterday, the component should not modify or destroy the props; it should merely view and use them. Let's define our component. Is everyone able to see this okay? We have a songs variable set by React.createClass. It’s a class with a render function. In this function, we're going to render an unordered list, grabbing the songs off of props, and we will iterate over it with map.
00:10:25.080 This is a little different than the Ruby code because we don't have blocks in JavaScript. Instead of using a block, we will call a function for each of these items, which will take the song and produce a list item with the song's name. To recap, we have a songs React class that has a render function that fetches songs from props, maps over them, calls a function for each song, and outputs a list item with the song name.
00:11:03.760 Let's practice by enhancing a Rails app that everyone here knows about. We'll take the 15-minute blog and incorporate some real-time comments into it. If you haven't seen the 15-minute blog, there is a blog component where we iterate over each comment and output the comment body, separated by an HR because it's the early 2000s.
00:11:52.720 In our React implementation, we'll create a Comment class with a render function that returns a div containing an HR and the comment, taken off props. In the view, we will use the React component helper to output the Comment component, passing in the comment body as a prop.
00:12:29.360 Our app transitions from looking like this to looking like that. The keen observer would note that they look exactly the same, which is correct. However, the primary distinction is that now the two comments are rendered in JavaScript. So, let's take it a step further and render all comments at once.
00:13:02.280 We'll create a Comments class, which is a React class that maps over each comment, pulls out the body, and renders it. React allows us to use our comment as if it were an HTML tag. So, we don't need to do anything special; we can just run it and pass the comment down as a prop.
00:13:36.360 Now our app goes from looking like this to looking like that. Again, the thorough observer would note that they look exactly the same, but now the entire comment block is rendered in JavaScript. This is pretty cool. Let's delve into the real-time aspect. We have a Comments component that renders out individual comments, and we need to create a Comments container.
00:14:05.560 Think of this container as analogous to a Rails controller. This Comments container knows how to fetch comments and which component to render when it has the comments ready. We will remove all the previous direct comment rendering code and now implement the Comments container by providing it with a path to fetch the comments.
00:14:42.120 Here's what the structure of the Comments container looks like. The first section deals with fetching comments, followed by the render function at the bottom, and we utilize a method called getInitialState for managing state effectively. This method provides an initial empty array, so our container knows the current state of comments.
00:15:02.360 We use the props passed before to get the path for fetching the JSON containing comments. On success, we utilize the setState method to provide the new data back into the component. This can be thought of as a refresh button for your component. Whenever new state is set, all components in the rendering tree will be updated.
00:15:34.920 We can use another lifecycle method called componentWillMount, which initiates when the component is ready. For this example, we will fetch the comments every second through polling. Now, when comments are added on one side, they will appear in real-time on the opposite side.
00:16:10.160 While this may not seem overly exciting, we didn't have to write extensive code to achieve this, and all the code is contained within those three component files. We did not have to change JavaScript files and update views all over the place; it stays isolated. We can also implement this differently in the future if needed.
00:16:55.760 React is very efficient in how it updates components. Although it may not be obvious, notice that only the last comment added is updated. When we set state by passing new comments, React renders the component in memory, then performs a diff with the DOM to update only the changed parts.
00:17:29.280 Let me summarize some key aspects of using React. First, this is how you render a component in your JavaScript or Rails views. You simply pass props in a straightforward way. For instance, we could create a Greeting component that says "Hi, Bob," which is a simple H2 element that interpolates a name.
00:18:00.400 The propTypes feature allows you to specify the type of the incoming name, which is particularly useful for component reusability. It helps catch errors in development, so if someone tries to pass in a number where a string is expected, they will receive a warning in the console.
00:18:37.760 There's also a getDefaultProps method that functions like setInitialState, where you can provide a default value. For example, if someone uses the Greeting component without passing a name, it will default to saying 'Guest.' SetState serves as the refresh button for your React components, while getInitialState allows you to set an initial state.
00:19:12.360 The componentWillMount lifecycle method is where you can fetch JSON data when the component is ready to go, ensuring that you manage updates and event listeners correctly as the component mounts and unmounts.
00:19:48.480 Overall, this amounts to about nine essential things you need to know to effectively use React. It may seem intimidating, but it's quite manageable. React's design means that you won't have to manually handle optimizing updates; it does that intelligently by itself.
00:20:23.760 For an example, let me show you our Services Live app. Notice how the clock on the app updates smoothly with just inner HTML being changed in real-time. This demonstrates the efficiency of React's rendering process.
00:21:04.360 I want to shift the focus to implementation. In the past, we used several different approaches for React, which created confusion. While I won't outline all the various methods we attempted, I will share the method we've settled on, which we believe to be the most effective and respectful to Rails and the asset pipeline.
00:21:44.560 We use the react-rails gem, which provides handy utilities, including the React component helper that we've been employing throughout the examples. It integrates smoothly with UJS for mounting and unmounting components. It's also compatible with TurboLinks, making it straightforward to set up.
00:22:19.360 You simply add the gem to your Gemfile and run the installer, which generates a components directory for you. The gem also provides a generator to create components. For instance, running react component greeting will produce the necessary code for rendering a component.
00:22:55.560 If you haven't used Rails assets, I recommend checking it out. It works with Bower, allowing you to pull in JavaScript libraries from repositories and generate genuine gems for integration with Rails apps. This approach greatly simplifies importing JavaScript libraries into your Rails applications.
00:23:38.760 We initially faced challenges by fighting the asset pipeline too much, experimenting with Browserify and Webpack without fully embracing the benefits of integration through the gem. For us, the simplest solutions yielded the best outcomes, leading to improved communication and satisfaction among our teams.
00:24:15.160 Now, addressing potential concerns, you might wonder about JSX. I don't see this as a major issue. Personally, I enjoy having JavaScript right there in my components; it’s convenient. It’s no different from using ERB or handlebars; all JSX does is preprocess into function calls.
00:24:46.280 React-Rails also supports CoffeeScript right off the bat. All you need to do is append Coffee on the back end, losing a few brackets and parentheses. However, one quirk is that you must backtick escape JSX, which can be annoying. As a result, we prefer to use ES6.
00:25:21.280 There is a small flag you can set in the React-Rails gem to help convert ES6 into ES5 seamlessly. We love using ES6 since it encapsulates much of what we liked about CoffeeScript without the need to actually use it.
00:26:01.480 In summary, you define components as classes extending React.Component rather than using createClass. You also pull out PropTypes and utilize constructor properties. What's remarkable about React is its ability to provide informed warnings when you try to use outdated APIs, guiding you on how to fix it.
00:26:43.360 Previously, we made the mistake of avoiding JSX and attempting odd implementations instead. However, embracing JSX is preferable, and it helps smooth out implementation details. Our most painful updates were those where we overlooked JSX and, consequently, missed essential aspects.
00:27:25.920 Moving forward, I encourage you to embrace ES6 as well. You’ll find that many examples and documentation in React are increasingly adopting this standard. This forward-thinking approach will certainly aid in working effectively with React.
00:28:06.160 As we wrap things up, I want to impart the idea of limiting your JavaScript use to only what is absolutely necessary. The core message is to stick to the Rails path as much as possible while only doing the minimal amount of JavaScript needed. React assists with this philosophy.
00:28:47.680 The final visual metaphor is about the evolution of Rails apps. Visualize your Rails app as a cake, sprinkle it with JavaScript, and over time, it morphs into a client-side MVC framework. You can picture TurboLinks 3 as a more refined intersection where the sprinkles enhance the Ruby views while keeping the cake solid.
00:29:30.760 This is the celebration we’re enjoying with React.js at Planning Center. Thank you so much!
Explore all talks recorded at RailsConf 2015
+117