Source: 412 digital

Many developers starting their adventure with React.js ask me about one thing. How to mount many independent React.js components on a single page? I’ll show you my approach to handle this problem.

What’s the problem?

We have some JavaScript applications showing user information using React.js components. We are good developers, so each one of them handles a separate responsibility.

We have applications, but now we need to put them all together on a screen.

Case Study - game dashboard

Some time ago I was working on a simple game inspired by CivClicker. I wanted to have one big screen with primary information about my virtual city. I developed applications responsible for game control and city management: resources, society and infrastructure.

Each rectangle represents separate app

Getting stuff done

1. Let backend handle that - quick and easy approach

We can use Rails views to solve our problem. Only thing we need is to expose empty HTML elements.

<!-- app/views/dashboard/dashboard.index.html.erb -->
<div id="game">
    <div data-app="gameHeader"></div>
    <div class="left-column">
        <div data-app="cityResources"></div>
        <div data-app="citySociety"></div>
    </div>
    <div class="right-column">
        <div data-app="cityInfrastructure"></div>
    </div>
</div>

And now in each of our application would need code like this:

App = require('city_infrastructure/app')

$(document).ready ->
  node = document.querySelector('[data-app="cityInfrastructure"]')
  app = new App()
  app.start(node)

We got basic HTML structure covered and empty divs for React to plug-in. It works great, but what if we would want to add some logic here?

Example: we want to show the City Infrastructure widget after the player reached the level 2. We can change our code.

<!-- app/views/dashboard/dashboard.index.html.erb -->
<div id="game">
    <div data-app="gameHeader"></div>
    <div class="left-column">
        <div data-app="cityResources"></div>
        <div data-app="citySociety"></div>
    </div>
    <div class="right-column">
        <% if @player.level > 1 %>
          <div data-app="cityInfrastructure"></div>
        <% end %>
    </div>
</div>

Our view isn’t dead simple anymore. The controller responsible for rendering this view needs to pass the player object to the template engine. Our code just got more complex. In future development it may get worse as it grows.

It’s a good solution for a start. Let’s move on. We will use React.js and some event bus to help us with this issue.

2. JavaScript app - more dynamic and elastic approach

Let’s make a simple JavaScript application that will render empty HTML elements for other applications.

Here’s the main idea. When all elements get rendered, the global event bus tells all applications about this fact. We will use the componentDidMount method from React component’s API to achieve this.

{div} = React.DOM
EventBus = require('modules/event_bus')
CurrentPlayer = require('modules/current_player')

DashboardSkeleton = React.createClass
  displayName: 'DashboardSkeleton'

  # It is launched after the React view is rendered
  componentDidMount: ->
    @props.eventBus.publish 'cityHeaderDivRendered'
    @props.eventBus.publish 'cityResourcesDivRendered'
    @props.eventBus.publish 'citySocietyDivRendered'
    @props.eventBus.publish 'cityInfrastructureDivRendered' if @showCityInfrastructure()

  showCityInfrastructure: -> @props.currentPlayer.level > 1

  render: ->
    div
      id: 'game'
      @header()
      @leftColumn()
      @rightColumn()

  header: ->
    div dataApp: 'gameHeader'

  leftColumn: ->
    div className: 'left-column'
      div dataApp: 'cityResources'
      div dataApp: 'citySociety'

  rightColumn: ->
    div className: 'right-column'
      div dataApp: 'cityInfrastructure' if @showCityInfrastructure()

Gui = React.createFactory DashboardSkeleton

class DashboardApp
  constructor: ->
    @eventBus = EventBus
    @startupGui = Gui(eventBus: @eventBus)

  start: (node) ->
    React.render(@gui, node)

Only thing left to do are event handlers for all applications.

class CityInfrastructureApp
  constructor: ->
    # ...
    @registerGuiStartup()

  registerGuiStartup: ->
    @eventBus.on 'cityInfrastructureDivRendered', =>
      node = document.querySelector('[data-app="cityInfrastructure"]')
      React.render(node, @gui)

This simple solution gives us flexibility for further changes. We can use all benefits of a dynamic front-end without introducing new libraries. Moreover, using this approach we gained new feature for free. We can render any app anytime during application life cycle. We just need to publish an event.

Often, the first solution is good enough. Yet, it gets problematic when we need to add some logic to the application. We can move this logic to the front-end and simplify our Rails backend.