Friday hack: Suspense, Concurrent mode and lazy to load locales for i18n
Tags: react, javascript, i18n
Published: 2018-12-15 04:13:02

---
title: Friday hack: Suspense, Concurrent mode and lazy to load locales for i18n
published: true
description: 
tags: [react, js, i18n]
cover_image: https://thepracticaldev.s3.amazonaws.com/i/qz8jv655155c37byozj0.jpg
series: i18n of React with Lingui.js
---

I have a small series of posts about Lingui. I implemented all i18n related features. And I want to add prerendering to improve load performance, but it appears not that simple as it supposes to be. I had to "hack" `Suspense`, `ConcurrentMode` and `React.lazy`.

As I said this is a hack, this is done for fun. Do not use this code in production, unless you know what you are doing.

The full source code is [here](https://github.com/stereobooster/react-lingui-example)

## In the previous episode

We stopped here: [i18n of React with Lingui.js #3](https://dev.to/stereobooster/i18n-of-react-with-linguijs-3-5h6g). I deployed it to Github pages and measured load performance with [webpagetest](https://www.webpagetest.org) (From: Dulles, VA - Moto G4 - Chrome - 3G).

![filmstrip 1](https://thepracticaldev.s3.amazonaws.com/i/x7t42yggrpp6r1hvjqx9.png)

As you can see it takes way to long to get first paint (4-4.5s). The easiest way to fix it, given that we use CRA and don't want to eject, it to use [react-snap](https://github.com/stereobooster/react-snap).

## Add prerendering with the help of react-snap
 ```shell
npm install --save react-snap
 # or using Yarn
yarn add react-snap
```

 Add `postbuild` hook to the `package.json`:

 ```json
"scripts": {
  "postbuild": "react-snap"
}
```
 And you're done!

I also added 

```
"reactSnap": {
  "inlineCss": true
}
```

![filmstrip](https://thepracticaldev.s3.amazonaws.com/i/999fd7o1xyxe55tnbcex.png)

As you can see an issue with slow first paint went away, but there is a flash of the white screen.

### Flash of the white screen

On the one side, we have prerendered HTML which will start to render as soon as the browser will get it (around 2s in the US on average 3G). On the other side, we have React which will start to render as soon as all scripts will be downloaded (around 3s in the US on average 3G, for the given example).

When React will start to render and if not all dynamic resources will be loaded it will flush all the content it has and typically this is the almost white (empty) screen. This is where we get "Flash of the white screen". Dynamic resources can be: async components (`React.lazy(() => import())`), locale catalogs (`import("./locales/" + locale + "/messages.js");`).

To solve the problem we need to wait for all resources to load before React will flush the changes to the DOM.

We can do this with loader library like, `react-loadable` or `loadable-components`. See more details [here](https://github.com/stereobooster/react-snap#async-components).

Or we can do this with new `React.lazy`, `<Suspense />` and `<ConcurentMode />`.

### ConcurentMode

`<ConcurentMode />` marked as unstable (use at your own risk), so it can change in the future. Read more on how to use it and about caveats [here](https://github.com/stereobooster/react-async-issue).

```js
const ConcurrentMode = React.unstable_ConcurrentMode;
const RootApp = (
  <ConcurrentMode>
    <Suspense fallback={<div>Loading...</div>} maxDuration={5000}>
      <App />
    </Suspense>
  </ConcurrentMode>
);
const rootElement = document.getElementById("root");
const root = ReactDom.unstable_createRoot(rootElement, { hydrate: true });
root.render(RootApp);
```

This is the first hack we need.

The second one is that we need to repurpose `React.lazy` to wait for subresource. React team will eventually add `Cache` for this, but for now, let's keep hacking.

```js
const cache = {};
export default ({ locale, children }) => {
  const SuspendChildren =
    cache[locale] ||
    React.lazy(() =>
      i18n.activate(locale).then(() => ({
        __esModule: true,
        default: ({ children }) => (
          <I18nProvider i18n={i18n}>{children}</I18nProvider>
        )
      }))
    );
  cache[locale] = SuspendChildren;
  return <SuspendChildren>{children}</SuspendChildren>;
};
```

- `i18n.activate(locale)` returns promise, which we "convert to ES6" module e.g. `i18n.activate(locale).then(() => ({ __esModule: true, ...}))` is equivalent to `import()`.
- `default: ...` - default export of pseudo ES6 module
- `({children}) => <I18nProvider i18n={i18n}>{children}</I18nProvider>` react functional component
- `<SuspendChildren />` will tell `<Suspense />` at the top level to pause rendering until language catalog is loaded

`<ConcurentMode />` will enable `<StrictMode />` and it will complain about unsafe methods in `react-router`, `react-router-dom`. So we will need to update to beta version in which issue is fixed. [`react-helmet` also incompatible with `<StrictMode />`](https://github.com/nfl/react-helmet/issues/426), so we need to replace it with `react-helmet-async`.

One way or another but we "fixed" it.

![](https://thepracticaldev.s3.amazonaws.com/i/tuchbvoaw0ug0wnphppc.png)

_Photo by Pankaj Patel on Unsplash_