How to “useState” in an Isomorphic React App?

An isomorphic app complements your React web-app with server-side-rendering. Users can see your content directly after the browser’s initial HTTP-request returns. They don’t have to wait for a script to download your app and render it locally.

So far so good.

But there’s a problem… (wait for it)

Any meaningful web-app works with data. You may want to load some content dynamically. Rather than hardcoding it in your app. You may want to show some user-specific data. You may even want to personalize the whole user experience.

Of course, you want your server-side-rendered web-app to include all the data.

The recently introduced React hooks have dramatically changed the way we handle local data. The useState-hook lets you preserve and change your local data easily.

The following code snippet is the official example of the [useState](https://morioh.com/p/55c9bdee7a63)-hook.

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

The useState-hook.

At line 5, you define a state-variable with an initial value of 0. You get the current value (const count) of this variable and a function (const setCount:(newValue) => void) that changes this state variable.

…the problem is: React useState-hooks do not work in an isomorphic context.

When React renders your components to HTML on the server-side, it removes all JavaScript from it. Along with the JavaScript, React removes all data in variables and all the state from your components. The resulting HTML is stateless. This is the dehydration-step.

It is left to the browser front-end to reintegrate the JavaScript into the HTML. This is the rehydration-step. But the data and the states are gone. Your components need to recalculate them and rerender.

What sense would server-side-rendering make, if the front-end needed to repeat all the steps? Not much!

But, there’s a way out. Before the dehydration removes the JavaScript from your components, you extract the data and the states. Then, you put this data into the HTML. Finally, before React rehydrates your app, you put the data and the states back into the components. When they render, they have all the data and the states. There’s no need for recalculation. There’s no need for rerendering.

But you have to do all these things manually. React does not help you much. You even need to manually identify the components that have some data or state. All this code is not very React-like.

Why is there no React-Hook for this? Or, at least something as easy?

There is!

Infrastructure-Components provide a useState-like hook that works in an isomorphic context. It makes sharing data and states between server and client a piece of cake: a one-liner!

The following code depicts a complete(!) isomorphic React app with server-side-rendering and isomorphic state. Let’s first look at the code. I’ll explain it right after.

import React from 'react';
import {
    Environment,
    IsomorphicApp,
    Route,
    WebApp,
    withIsomorphicState
} from "infrastructure-components";

export default (
    <IsomorphicApp
        stackName = "isomorphic-state"
        buildPath = 'build'
        assetsPath = 'assets'
        region='eu-west-1'>

        <Environment name="dev" />

        <WebApp
            id="main"
            path="*"
            method="GET">

            <Route
                path='/'
                name='Isomorphic-State'
                render={withIsomorphicState((props)=>{
                    const [count, setCount] = props.useIsomorphicState("counter", 0);

                    // rerender 5 times ... but only at the server-side
                    console.log("current count: ", count);
                    if (count < 5) {
                        setCount(count + 1);
                    }


                    return <div>
                        <p>You clicked {count} times</p>
                        <button onClick={() => setCount(count + 1)}>
                        Click me
                        </button>
                    </div>
                })}
            />
        </WebApp>
    </IsomorphicApp>
);

A full isomorphic React app.

Note: In the example, we change the state within the render function. This causes a rerender. This is far from being best practice! its purpose is to illustrate the differences of server-side-rendering and client-side-rendering.

Infrastructure-Components-based projects have a clear structure. They have a single top-level component. It defines the overall architecture of the app. Sub-components (children) refine the app’s behavior and add functions.

In the example, the <IsomorphicApp/>-component is the top-level component. We export it as default in our entry point file (src/index.tsx).

The <WebApp/>-component depicts our web-application. It consists of <Route/>- components. These are the pages of our app. They work like the <Route/>s in react-router. They contain the user interface of our app. Everything you see. Everything you can interact with.

In therender-function, we use the higher-order-component withIsomorphicState that we import from 'infrastructure-components'. It adds the hook-like function useIsomorphicState into the props of the component.

This function works like the useState-hook. It provides the current value of the variable and a function that allows you to set a new value. There’s only a small difference. The useIsomorphicState -function takes two parameters. The first is an identifier. A simple string. This must be unique across your app.

Let’s Get Our Hands Dirty

Enough with the theory. Let’s see how it works in practice.

This GitHub-repository contains the example that you can fork or clone. If you want to get a customized boilerplate code, you can use the Infrastructure-Components-configurator:

  1. Select your architecture, specify a name for your React app (like myapp) and the name of an environment, (e.g. dev)
  2. Download and unpack your customized boilerplate code

Once you got the project files in the directory of your choice, you can install all the dependencies. Run: npm install (maybe you require sudo-rights).

Then you can build the project. Run: npm run build. You only need to build your project once.

You can run it locally, including its backend: npm run start-dev. If you change your source code, you need to stop the app (ctrl + c) and restart it.

Open localhost:3000 once the scripts tell you that your app is running. You should see the counter.

But did the app render at the server-side? Let’s check!

Open the developer tools of your browser (I use Chrome, but other browsers provide similar tools). Open the network-tab (you may need to reload the page after you opened the tab to see the loaded resources).

When you click at the localhost -resource and open preview, you can see how the HTML looked like when it came from the server. You can already see the initial value set to 5.

How to “useState” in an Isomorphic React App

Preview of the resource rendered at the server-side

Our server already rendered the component five times!

The console output in your IDE displays the logs of your server. You can see that the counter starts at 0 and counts up to 5. This is the console.log in line 31.

How to “useState” in an Isomorphic React App

The IDE displays the server logs.

The console tab in your browser shows the logs of your front-end. You can see that it starts with 5 already. It does not rerender anymore.

How to “useState” in an Isomorphic React App

The browser displays the front-end logs.

What’s next?

In this post, we’ve learned about how the useIsomorphicState-hook lets you easily share the state between the server and the client in an isomorphic React app.

#reactjs

How to “useState” in an Isomorphic React App?
10.20 GEEK