Talks

React Native & Rails, A Single Codebase for Web & Mobile

React Native & Rails, A Single Codebase for Web & Mobile

by Ben Dixon

In the RailsConf 2017 talk "React Native & Rails, A Single Codebase for Web & Mobile," Ben Dixon discusses the effectiveness of utilizing React Native alongside Rails to create a streamlined development process for both web and mobile applications. Dixon shares his experiences transitioning from other technologies, highlighting the advantages of React's versatile framework which allows for code reuse across platforms.

Some of the key points discussed include:

  • Development Productivity: Dixon emphasizes how Rails enables rapid development, mentioning that React Native offers similar productivity gains for mobile development. He contrasts this with his prior experiences with other frameworks where he felt bogged down by repetitive tasks.
  • Unified Codebase: By using React for both web and mobile applications, developers can maintain a shared codebase which reduces the overall effort required to develop and manage applications across different platforms. This shift results in a minor increase in effort (about 10%) when adding new platforms rather than tripling the workload.
  • React as a View Layer: Dixon clarifies that React serves primarily as a view layer and does not provide built-in utilities for making API calls or managing persistence, necessitating the use of other libraries to fill these gaps. He discusses the use of patterns like Redux for managing application state to simplify this process.
  • Component Structuring: He addresses how to structure components to keep UI differences between mobile and web applications separate while sharing other parts like state management and API calls. This approach leads to a more organized and maintainable codebase.
  • Best Practices: Dixon shares practical lessons and pitfalls he encountered during this transition, including the importance of properly managing Babel configurations and using generators to streamline code structure. He also provides insight into setting up shared modules within an application, which can ease transitions between web and mobile development.

Overall, Dixon concludes that this approach to development not only enhances productivity but also reassures developers that they can expand to native applications without the fear of overwhelming complexity or resource demands. The session finishes with Dixon inviting participants to explore a sample project demonstrating these principles in action, emphasizing a collaborative and incremental approach to building applications using Rails and React.

00:00:11.780 Hey, everyone! What I've always loved about Rails is just how productive I feel when I'm using it.
00:00:20.369 Before Rails, I spent a lot of time working with Python, Django, PHP, .NET, and various frameworks. I like many aspects of them, but nothing has come close to Rails.
00:00:27.630 The combination of Rails as a framework and its language allows me to move quickly from an idea to a working version. While working in Rails, I don't feel like I spend a lot of time solving web development problems; instead, I focus on the unique issues related to the project at hand.
00:00:39.809 On the other hand, when it comes to mobile application development, I haven't experienced that same level of efficiency. I've spent time building native mobile applications in Java for Android, and although I appreciate the languages and the tooling, I often felt like I was spending too much time on basic mobile development problems.
00:00:52.020 Things like making API calls and persisting data—tasks that I assume every mobile developer has to solve—have taken me a significant amount of time. If you also consider cross-platform support for iOS and Android alongside a web version, it can require three developers to deliver features consistently across the board.
00:01:03.809 This has always led me to resist the push for native development, as it tends to lead to a substantial decrease in productivity. Over the years, I've sought after magic solutions, which, of course, don't exist.
00:01:16.409 I've built HTML5 web applications and deployed them with PhoneGap, as well as explored compiled native solutions like Appcelerator. While both approaches have their merits, I've not managed to create an HTML5 web application that feels genuinely native.
00:01:28.049 These applications feel more like web applications that are just marginally better on mobile. Compiler frameworks can also lead to challenges in testing and maintenance, diluting their supposed benefits.
00:01:39.060 However, about 18 months ago at Catapult, where I work, we began experimenting with React for our web applications. Initially, we made a lot of mistakes, but within a few months, we arrived at a stack we were quite happy with and successfully converted our front-end to React.
00:01:52.020 React is famously promoted by Facebook as 'Learn once, write anywhere,' instead of 'Write once, run anywhere.' I was skeptical of this claim initially because it felt like many platforms promised similar compatibility.
00:02:06.240 Nevertheless, we were pleasantly surprised to discover that most of what we learned while building React applications for the web carried over to React Native for mobile. It's possible to make the same mistakes on both platforms, but the architectural principles and libraries we used in React Web often applied directly to React Native.
00:02:18.420 This was fantastic because it meant any developer who was already familiar with our web codebase could quickly adapt and work on our mobile codebase, which was incredibly exciting.
00:02:30.240 From this point, we considered why we couldn't share our own code across platforms since libraries are essentially just reusable code written by others.
00:02:41.250 This led us to the realization that a significant amount of our business logic—that is to say, the majority of our code—could now be shared across entirely native applications as well as our web applications.
00:02:53.670 The outcome of this was that if we wanted to develop native applications and a completely separate web interface, the effort required to support three platforms could decrease to an additional 10% for each extra platform, rather than tripling our development resources.
00:03:05.730 As a result, I no longer felt like the person in the back of the room resisting the decision to pursue native development, fearing that we would need significantly more developers to accommodate that change.
00:03:18.090 So, I'm Ben, by the way. I'm the CTO of Catapult, a staffing platform based in London. I've spoken at conferences like this a few times before, typically covering topics related to Docker or server deployment.
00:03:25.770 Although I consider myself a full-stack developer, I feel much more comfortable setting up infrastructure or building APIs than I do with front-end development.
00:03:37.140 That changed when I started working with React. I want to give this talk because this React and React Native stack is the first time I really felt like the tools made me as productive building front-end applications as I am building Rails applications.
00:03:49.500 There are three areas I want to cover: First, a high-level overview of what a production React application looks like and its components; second, a discussion of the important considerations for our stack, especially regarding minimizing friction when sharing code between web and native codebases.
00:04:00.180 Finally, in a more detailed exploration, I’ve reflected on where I wasted the most time over the past 18 months and the sort of things I wish someone had pointed out to me early on.
00:04:12.660 The first important takeaway is that React is not a framework; it’s not like Angular, which includes almost everything necessary to build a front end or JavaScript application.
00:04:23.790 React essentially provides just a view layer. It offers a declarative model for defining components, allowing us to pass props and data into those components.
00:04:31.500 React then determines what needs to be rendered based on that input. When building for the web, this means using ReactDOM and generating HTML. For mobile, it involves using native components through React Native.
00:04:42.120 However, React doesn’t provide solutions for API calls or data persistence, which can be confusing when people refer to features as built-in React.
00:04:54.240 What is generally meant by this is that React is being used as a view layer alongside a collection of other libraries fulfilling the rest of the requirements.
00:05:07.200 As a Rails developer, I am somewhat spoiled because we often have a community consensus on what to use—if it’s not provided by Rails, there’s a well-accepted solution.
00:05:19.100 In the JavaScript ecosystem, however, that can lead to a very stressful environment, as you may be told to use one library this week and another the next.
00:05:29.940 What I'm going to present now is by no means a definitive React stack; rather, it’s one we have been very satisfied with and the one I've observed being used across various large React codebases.
00:05:41.220 This stack has proven to be very friendly, especially if your main goal is to maximize code reuse across web and mobile applications.
00:05:53.220 A typical React stack will need four main components: a Router, which in React terms serves to map URLs to specific components that need to be rendered.
00:06:06.420 In a native mobile application, the Router functions similarly; it recognizes when you want to display a login screen and determines which components should be rendered for that.
00:06:19.920 When discussing React, we'll also require an implementation of React for the web, which is ReactDOM, and for mobile, we’ll employ React Native.
00:06:31.000 Managing global application state in React is less straightforward, as React itself does not prescribe a standard way to do so. There are, however, widely adopted patterns in the community.
00:06:45.210 Many developers may refer to these concepts using the Flux pattern or Redux, which is my preferred implementation.
00:06:58.500 These patterns have emerged largely in response to the challenges in managing state within modern JavaScript applications.
00:07:10.999 In a typical JavaScript application, your state might be stored in a variety of models, potentially updated by various callbacks and API calls, leading to complex interactions that can create race conditions.
00:07:23.290 Using Redux, however, allows for a simplified and predictable state management system where the only way to change application state is by dispatching an action.
00:07:34.290 Actions describe the changes we want to make, and reducers handle these actions by accepting the current state and returning a new state based on the action's type.
00:07:46.840 This process creates a clear, linear approach to managing application state, providing us with a deterministic way to understand how our application is functioning.
00:07:59.210 By subscribing to state updates, different parts of the application, including the UI, can react to changes in state without directly altering the state.
00:08:10.350 This approach benefits from powerful tooling that allows us to step back through state changes, even rewinding to earlier states of the application.
00:08:24.076 Let’s look at a very simple example: imagine an app with a button that increments a counter. The counter’s goal is to reach the highest number possible.
00:08:39.820 We begin with an initial state represented as a JavaScript object with properties such as count, user name, and rank.
00:08:50.720 In a traditional JavaScript app, upon clicking the button, we would directly update this object. However, using Redux, instead of making this direct modification, we dispatch an action.
00:09:03.490 The dispatched action is a simple JavaScript object that contains a type property (e.g., ‘increment’) and its relevant payload.
00:09:15.570 We then pass both this action and the application's current state into a reducer function, which evaluates the action type and decides how to modify the state accordingly.
00:09:26.800 The reducer operates without modifying the original state object; instead, it creates a new object with the updated state.
00:09:40.100 The rest of the UI can subscribe to these state changes to respond accordingly, ensuring that actions are processed in a sequential manner.
00:09:53.500 Now we need to address side effects, a term that can be a bit confusing. When people refer to side effects, they usually mean asynchronous actions.
00:10:05.890 In our counter application, for example, if we wanted to maintain leaderboards due to its popularity, we would need to perform API calls whenever the button is pressed.
00:10:17.700 In this case, utilizing a library like Redux Saga can help us manage these asynchronous effects. Redux Saga listens for dispatched actions and can trigger the necessary API calls.
00:10:30.060 Once the asynchronous action is completed, Redux Saga dispatches additional actions to update the state with the latest information.
00:10:44.460 These four components—the Router, state management, the action handling, and side effects—form the foundation of our application architecture.
00:10:59.400 Next, we need to consider what aspects can and should be shared between web and mobile codebases.
00:11:12.130 Originally, I was not interested in creating HTML5 versions of native applications because I hadn't encountered many production applications where the web version was simply a scaled-up version of the mobile one.
00:11:24.090 My assumption has generally been that the view layers of mobile and web applications would differ significantly in structure and layout.
00:11:39.240 For instance, a form that takes up one screen on a web application may require three, four, or even five separate screens on mobile. Therefore, I have avoided sharing the view structure.
00:11:51.270 This thought process aligns with how we build Rails applications. If we develop a web application and decide to add a JSON API, we keep the business logic in models, service objects, and maintain a separate, clean view layer.
00:12:02.760 We wouldn't consider mixing a single view for a JSON API with a traditional HTML view, as they serve very different purposes.
00:12:14.310 Applying this reasoning helps not only maintain structure but also maximize efficiency when sharing code. The critical pieces tend to revolve around state management and side effects, which can easily be shared.
00:12:26.390 Thus, we see significant benefits in sharing those components.
00:12:37.440 For practical examples, nearly every application includes a login page. It begins with a Router that dictates which components to display.
00:12:49.620 The view layers will likely differ across web and mobile platforms, but there are commonalities when addressing the login button's functionality.
00:13:01.440 Regardless of the platform, the action that occurs when the login button is clicked boils down to dispatching an action aimed at updating the application's global state.
00:13:15.840 We would want to set flags that indicate the app is processing a login, allowing the UI to display loading indicators as necessary.
00:13:27.630 Simultaneously, we would initiate an API call to login the user, using our side effects to manage the login action.
00:13:42.240 This API call will return a success or failure. When successful, we should update our state accordingly, hide the loading indicator, and store any related data such as the login token.
00:13:55.470 Ultimately, we want to redirect the user to a dashboard or home page, and this logic may need to interface with the Router.
00:14:09.330 This is where the separation of concerns begins to blend because the side effect has to interact with components that aren’t shared.
00:14:23.550 Thus, we may end up needing an if-condition for web and mobile or disconnecting the side effect entirely from that logic.
00:14:36.300 At the beginning of this login flow, when the login button is pressed, the view component contains both the button handler and callbacks for success or failure, which can inform the Router to carry out proper navigation.
00:14:52.730 This keeps the side effect shareable while allowing the component to know how to interact with the Router.
00:15:04.540 Ideally, following this pattern allows us to separate our business logic while sharing actions, reducers, and side effects, creating a structure akin to that of a standard React application.
00:15:17.860 This has made it much easier for developers to transition between mobile and web development; they can contribute seamlessly to one or the other.
00:15:30.300 I would like to clarify that there is indeed a level of complexity involved, especially regarding native functionality, such as when a mobile app wants to access device GPS.
00:15:43.170 In such cases, asynchronous behavior is required, making it difficult to create a genuinely shared component.
00:15:56.380 I learned the hard way that while it’s important to strive for shared operations, sometimes it's necessary to tailor solutions for unique functionalities.
00:16:09.780 This point emphasizes the way we divide the overall architecture of the application, balancing standardized approaches while allowing exceptions.
00:16:21.390 Fundamentally, the goal is to build maintainable and scalable applications where the shared code makes overall development more efficient.
00:16:35.340 Before wrapping up, I want to stress that following the outlined patterns for organizing code has created a productive development environment.
00:16:47.750 This has made it so much easier for us to onboard new developers since most tutorials available for React web will be applicable to React Native.
00:17:01.620 This familiarity reduces the learning curve and complexities, leading to smoother transitions in projects regardless of the specific platform being worked on.
00:17:17.430 What I want to share with you today largely covers architectural insights and practical advice from my experience.
00:17:29.060 The productivity gains we’ve seen using this stack have shifted my thinking significantly about when to start integrating native applications into projects.
00:17:45.390 There's an ongoing shift to what we realistically expect when supporting both web and native applications, so the changes in approach have become valuable.
00:17:58.890 I’ve gone ahead and created a sample project available at cookie-dough.co.uk/railsconf2017 that illustrates this sharing in practice. It includes both iOS and web components along with a Rails API.
00:18:10.740 It's open-source, allowing anyone interested to see its workings and explore which parts resonate and which don’t.
00:18:24.480 As we wrap up, I want to mention that at Catapult, we are hiring! If you are intrigued by what we've discussed, please reach out to me at [email protected], or let’s grab coffee if you're around London.
00:18:42.540 Thank you for listening. If you have any questions, I’ll be happy to answer them now.
00:19:02.090 Absolutely! I encourage everyone to ask your questions.
00:19:16.350 When it comes to organizing actions in large files, I’m fond of using Redux Source, which is syntactic sugar on top of Redux.
00:19:24.720 It provides a framework for breaking down multiple reducer files while maintaining proper structure.
00:19:36.920 Great question! To support responsive interfaces for web versions, I initially swayed towards a framework like Bootstrap.
00:19:49.610 It simplifies creating mobile-friendly web applications, greatly reducing the complexity involved.
00:20:01.620 In parallel, Microsoft has released a framework that permits component definition and chooses between web and mobile layouts.
00:20:15.680 How do we handle customization in platforms with diverse components? Honestly, I don't see many display discrepancies.
00:20:29.820 Generally, it’s good to lean on React as it handles platform-specific components very well.
00:20:44.560 Is service architecture beneficial over old server-side Rails applications? Initially, Rails is quite rapid for prototyping.
00:20:58.260 Yet, over time, adopting API servers alongside client-side architectures yields greater maintainability.
00:21:11.950 Despite the initial speed of Rails, I've found the efficiency and structure of separating APIs and front-end heavily advantageous.
00:21:26.800 When it comes to developing native applications, learning the SDK is essential, but the React Native CLI streamlines this process.
00:21:40.680 React Native provides a minimal setup to help kickstart projects without excessive boilerplate.
00:21:54.590 As a Rails developer, dealing with boilerplate can be annoying, so I leverage tools like Ignite to automate setups.
00:22:09.570 This ensures consistency and organization from the get-go, allowing for easier extraction into NPM packages.
00:22:23.020 Lastly, I want to emphasize the importance of managing Babel configurations across projects.
00:22:38.240 Babel is a tool that helps transpile modern JavaScript to make it compatible with older browsers, which is crucial for React web applications.
00:22:52.070 However, there's a risk of Babel configuration conflicts if not managed carefully, especially when integrating with React Native.
00:23:06.360 Realistically, changes to Babel can lead to sporadic runtime errors, so consistent management of the cache is essential.
00:23:21.050 I hope this overview captures the critical elements of the architectural decisions we’ve faced while integrating these technologies.
00:23:35.500 Ultimately, the changes to our processes have greatly influenced productivity and adaptability for new features.
00:23:49.490 I invite any final questions before we wrap things up. The shift in mindset has been transformative.
00:24:03.390 What I want to underline is the vital and positive shift we’ve experienced, allowing us to integrate diverse platforms seamlessly.
00:24:20.150 Once again, I want to thank you all for participating in this session. It’s been enlightening to share these experiences with you!