FOUC issue in React [SOLVED!]

FOUC

FOUC - so called Flash of Unstyled Content can be as very problematic as so many tries of solving this issue.

To the point

Let’s consider following configuration of routing (react-router):

...
<PageLayout>
  <Switch>
    <Route exact path='/' component={Home} />
    <Route exact path='/example' component={Example} />
  <Switch>
</PageLayout>
...

where PageLayout is a simple hoc, containing div wrapper with page-layout class and returning it’s children.

Now, let’s focus on the component rendering based on route. Usually you would use as component prop a React Compoment. But in our case we need to get it dynamically, to apply feature which helps us to avoid FOUC. So our code will look like this:

import asyncRoute from './asyncRoute'

const Home = asyncRoute(() => import('./Home'))
const Example = asyncRoute(() => import('./Example'))

...

<PageLayout>
  <Switch>
    <Route exact path='/' component={Home} />
    <Route exact path='/example' component={Example} />
  <Switch>
</PageLayout>

...

to clarify let’s also show how asyncRoute.js module looks like:

import React, { Component } from 'react'
import PropTypes from 'prop-types'

import Loader from 'components/Loader'

class AsyncImport extends Component {
  static propTypes = {
    load: PropTypes.func.isRequired,
    children: PropTypes.node.isRequired
  }

  state = {
    component: null
  }

  toggleFoucClass () {
    const root = document.getElementById('react-app')
    if (root.hasClass('fouc')) {
      root.removeClass('fouc')
    } else {
      root.addClass('fouc')
    }
  }

  componentWillMount () {
    this.toggleFoucClass()
  }

  componentDidMount () {
    this.props.load()
      .then((component) => {
        setTimeout(() => this.toggleFoucClass(), 0)
        this.setState(() => ({
          component: component.default
        }))
      })
  }

  render () {
    return this.props.children(this.state.component)
  }
}

const asyncRoute = (importFunc) =>
  (props) => (
    <AsyncImport load={importFunc}>
      {(Component) => {
        return Component === null
          ? <Loader loading />
          : <Component {...props} />
      }}
    </AsyncImport>
  )

export default asyncRoute

hasClass, addClass, removeClass are polyfills which operates on DOM class attribute.

Loader is a custom component which shows spinner.

Why setTimeout?

Just because we need to remove fouc class in the second tick. Otherwise it would happen in the same as rendering the Component. So it won’t work.

As you can see in the AsyncImport component we modify react root container by adding fouc class. So HTML for clarity:

<html lang="en">
<head></head>
<body>
  <div id="react-app"></div>
</body>
</html>

and another piece of puzzle:

#react-app.fouc
    .page-layout *
        visibility: hidden

sass to apply when importing of specific component (ie.: Home, Example) takes place.

Why not display: none?

Because we want to have all components which rely on parent width, height or any other css rule to be properly rendered.

How it works?

The main assumption was to hide all elements until compoment gets ready to show us rendered content. First it fires asyncRoute function which shows us Loader until Component mounts and renders. In the meantime in AsyncImport we switch visibility of content by using a class fouc on react root DOM element. When everything loads, it’s time to show everything up, so we remove that class.

Hope that helps!

Thanks to

This article, which idea of dynamic import has been taken (I think) from react-loadable.

Backbone (Marionette) to React

How it took place?

One day somebody gave me - a backend guy - an old school project written in Backbone with Marionette. At the beginning, I was trying to get into it, learn how it works etc, but after some while - at the time when React community was growing really fast - I said: That’s it! Let’s move to React.

The first thing was to convince my teammates to do it. Everybody wasn’t sure if it was a good idea: “We have tons of code lines…”, and I agreed.

From a business point of view, we couldn’t rewrite the whole Backbone app to React. We had to do it smoothly, piece by piece. Another thing was to decrease the amount of data (dist.js - containing the whole application logic) being sent to the client’s browser. Now I can say that we did it! In production.

I was trying to find a good article about this case. While reading many meaningless articles I came across this:

http://jgaskins.org/blog/2015/02/06/gentle-migration-from-marionette-to-react

And that’s really cool, anyway thanks, guys! But… we decided to go a little bit further.

Imagine that you’re not using gulp, Backbone with Marionette and you don’t build big dist.js, but you use webpack bundle loader and React.

How could that be possible?

Well you have to mix it. Create a connection between React and our lovely Backbone app. Let’s say: create a hybrid.

Recipe

I need you to focus. I won’t sell a boilerplate here, but I will try to explain HOW TO in a really easy and straightforward way.

First, we need to deal with webpack and install base React sources + React Router. We can even use simple boilerplate (we used React, React Router and Redux). Then, put everything in the same static directory next to the Backbone app. When we have it and by running npm start we will see a simple React app on http://localhost:8080, we can move to step two.

In that step we look into the Backbone app, find (or define) a place like this:

var App = new Marionette.Application({
  onStart: function(options) {
    // some code here
  }
});

and put a global variable there, let’s call it backboneAppReady and set it to true:

var App = new Marionette.Application({
  onStart: function(options) {
    // some code here
    window.backboneAppReady = true
  }
});

That will tell the React that the Backbone application has been started and we can talk with it. How? Let’s go to the next step.

We have something like Backbone.Wreqr (https://github.com/marionettejs/backbone.wreqr). Now it’s depreciated, but which old project isn’t? :) After installing it we can use radio channel, which is the key element.

Of course if you’re up-to-date with Marionette, you can use in Marionette v3.x.x built-in radio option with channels (https://marionettejs.com/docs/v3.0.0/backbone.radio.html#channel)

Now, I’m sure you have implemented msgbus, as a module, in the following way (if not I suggest to do that):

// static/backboneapp/utils/msgbus.js

define(["backbone", "backbone.wreqr"], function(Backbone) {
  var API;
  API = {
    vent: new Backbone.Wreqr.EventAggregator(),
    command: new Backbone.Wreqr.Commands(),
    reqres: new Backbone.Wreqr.RequestResponse()
  };
  return API;
});

the only thing we should change here is to use the power of channels! So change to this:

// static/backboneapp/utils/msgbus.js

define(["backbone", "backbone.wreqr"], function(Backbone) {
  var API, globalChannel;
  globalChannel = Backbone.Wreqr.radio.channel('global');
  API = {
    vent: globalChannel.vent,
    command: globalChannel.commands,
    reqres: globalChannel.reqres
  };
  return API;
});

Now you created a global channel to talk through.

If we have it, then we can move to React app. First, let’s create url dispatcher for our Backbone views:

// static/reactapp/routes/Backbone/dispatcher.js

const showHome = (msgbus, matches) => {
  msgbus.reqres.request('show:home')
}

const showMembers = (msgbus, matches) => {
  msgbus.reqres.request('show:company:members')
}

const showMember = (msgbus, matches) => {
  let id = matches[1]
  msgbus.commands.execute('show:company:member', {id: id})
}

const notFound = (msgbus, matches) => {
  msgbus.reqres.request('show:notfound')
}

const backboneAppRoutes = [
  [/^\/members/, showMembers],
  [/^\/members\/(([0-9]+)*)/, showMember],
  [/\//, showHome],
  [/.*/, notFound]
]

const dispatchUrl = (path) => {
  const msgbus = Backbone.Wreqr.radio.channel('global')

  for (let route of backboneAppRoutes) {
    let [regex, show] = route
    let matches = path.match(regex)
    if (matches) {
      show(msgbus, matches)
      return
    }
  }
}

export default dispatchUrl

then a util to deal with:

// static/reactapp/routes/Backbone/utils.js

import dispatchUrl from './dispatcher'

const showBackbonePage = (pathname) => {
  if (!window.eventoryBackboneReady) {
    return setTimeout(() => {
      showBackbonePage(pathname)
    }, 250)
  }
  dispatchUrl(pathname)
}

export default showBackbonePage

and finally the React NotFound.js view implementation:

// static/reactapp/routes/NotFound/components/NotFound/NotFound.js

import { Component } from 'react'
import PropTypes from 'prop-types'
import { isEqual } from 'underscore'

import showBackbonePage from 'routes/Backbone/utils'

class NotFound extends Component {
  static propTypes = {
    location: PropTypes.object.isRequired
  }

  constructor (props, context) {
    super(props, context)

    this.currentUrl = props.location.pathname + props.location.search
  }

  componentDidMount () {
    showBackbonePage(this.props.location.pathname)
  }

  componentDidUpdate () {
    showBackbonePage(this.props.location.pathname)
  }

  componentWillUnmount () {    
    const msgbus = Backbone.Wreqr.radio.channel('global') 
    msgbus.reqres.request('clear:backbone:page') 
  }

  shouldComponentUpdate (nextProps) {
    const url = nextProps.location.pathname + nextProps.location.search
    const shouldUpdate = !isEqual(this.currentUrl, url)
    if (shouldUpdate) this.currentUrl = url
    return shouldUpdate
  }

  render () {
    return false
  }
}

export default NotFound

Ooops! I forget about CoreLayout.js (called also PageLayout.js - example reference: https://github.com/davezuko/react-redux-starter-kit/blob/master/src/layouts/PageLayout/PageLayout.js):

import React, { Component } from 'react'
import PropTypes from 'prop-types'

class CoreLayout extends Component {
  static propTypes = {
    children: PropTypes.element.isRequired,
    location: PropTypes.oneOfType([PropTypes.oneOf([null]), PropTypes.object]),
    dispatch: PropTypes.func.isRequired
  }

  static contextTypes = {
    router: PropTypes.object.isRequired
  }

  constructor (...args) {
    super(...args)
    this.setLocation = this.setLocation.bind(this)
  }

  componentWillMount () {
    this.initLocationHandlers()
  }

  setLocation (params) {
    const pathname = params.pathname || '/'
    const queryParams = params.queryParams ? '?' + params.queryParams : ''
    this.context.router.push(pathname + queryParams)
  }

  initLocationHandlers () {
    if (!window.eventoryBackboneReady) {
      return setTimeout(() => {
        this.initLocationHandlers()
      }, 250)
    }
    const msgbus = Backbone.Wreqr.radio.channel('global')
    msgbus.reqres.setHandler('set:location', this.setLocation)
  }

  render () {
    return (
      <div className='main-wrapper'>
        <div className='core-layout__viewport'>
          <Header />
          <div className='core-layout__children'>
            {this.props.children}
          </div>
        </div>
        <div className='core-layout__backbone' />
      </div>

    )
  }
}

export default CoreLayout

What is setLocation and set:location handler for?

Until everything works fine (Backbone and React apps), we need to implement one location handler on the React app side and apply it to Backbone app, otherwise the browser history won’t work. In every place you navigate around Backbone app, you have to use msgbus in this way:

msgbus.reqres.request("set:location", {pathname: '/members'})

Now your Backbone app navigates through React Router routes.

Remember to remove Router from Backbone app, so this part:

class Router extends Marionette.AppRouter
    ...

and its usage.

What is clear:backbone:page handler for?

It’s made to clear main region content in case of changing the Backbone’s view to the React’s view. It’s called every time a NotFound.js component unmounts. You define it in the Backbone app. Here is the example implementation:

msgbus.reqres.setHandler("clear:backbone:page", function() {
  var mainRegion;
  mainRegion = App.getRegion("mainRegion");
  mainRegion.currentView.destroy();
});

How to organize the main regions of both apps?

You have to put Backbone’s root div into React’s root div which is recommended to mix views.

React’s root div is defined by the #main-region selector (see the index.html example below).

Backbone’s root div (mainRegion - from the previous section) is defined by the #main-region .core-layout__backbone selector (<div className='core-layout__backbone' /> in CoreLayout.js in code example above).

index.html to complete the view

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Backbone (Marionette) to React</title>
    <!-- React dist.js -->
    <!-- Backbone dist.js -->
  </head>
  <body>
    <div id="main-region"></div>
  </body>
</html>

In case of a need to escape the template tags which backend renders, you can use https://www.npmjs.com/package/ejs.

How it works?

The main assumption was to pass the whole traffic through the React Router. So we set NotFound.js route as in the example above and when applications starts, it searches route in React and if doesn’t find one then it goes to Backbone app.

Hope that helps!

Big thanks to

I’d like to thank my mate Dawid who went with me through all this stuff and we’ve got this done!

Current live goals

  • Write a scenario for a theatrical performance
  • Be fluent in i3wm
  • Be the frontend bastard
  • Finally run the marathon - injured..., but was close enough

God and nature do nothing without a purpose

Aristotle