My approach to SSR and useEffect - discussion
Tags: react, ssr, useeffect, discuss
Published: 2020-05-10 22:00:26

For the last few days, I was developing my personal website. I felt it needed some refreshment and as always it is a great occasion to play with something new. I've decided it will be written in React with SSR. 

I've put all data fetching in `useEffect` hook - pretty standard approach. However, `useEffect` does not play very well with server-side rendering. I've managed to work this out by creating custom hook `useSSE` - "use server-side effect" and I've created an npm package from it.

I am very curious about your opinion. Here is the package on [npm](https://www.npmjs.com/package/use-sse) and GitHub repo: 
{% github kmoskwiak/useSSE no-readme %}
And here is an example on [CodeSandbox](https://codesandbox.io/s/falling-waterfall-wnlwc?file=/README.md).

## And this is how it works... 

Instead of using `useEffect` for data fetching, I use `useSSE`. It looks like a combination of `useState` and `useEffect`. Here is an example:

```js
const MyComponent = () => {
  const [data] = useSSE(
    {},
    "my_article",
    () => {
      return fetch('http://articles-api.example.com').then((res) => res.json());
    },
    []
  );

  return (
  	<div>{data.title}</div>
  )
}
```

`useSSE` takes 4 arguments:

- an initial state (like in `useState`)
- a unique key - a global store will be created, and data will be kept under this key,
- effect function returning promise which resolves to data,
- array of dependencies (like in `useEffect`)

The essence of this approach is to render application __twice__ on server. During first render all effect functions used in `useSSE` hook will be registered and executed. Then server waits for all effects to finish and renders the application for the second time. However, this time all data will be available in global context. `useSSE` will take it from context and return in `[data]` variable.

This is how it looks on server-side. The code below shows a part of `expressjs` app where the request is handled. 

```js
app.use("/", async (req, res) => {
  // Create context
  // ServerDataContext is a context provider component
	const { ServerDataContext, resolveData } = createServerContext();

  // Render application for the first time
  // renderToString is part of react-dom/server
	renderToString(
		<ServerDataContext> 
		    <App />
		</ServerDataContext>
	);

  // Wait for all effects to resolve
	const data = await resolveData();
	
  // My HTML is splited in 3 parts
	res.write(pagePart[0]);
	
  // This will put <script> tag with global variable containing all fetched data
  // This is necessary for the hydrate phase on client side
	res.write(data.toHtml());

	res.write(pagePart[1]);

  // Render application for the second time. 
  // This time take the html and stream it to browser
  // renderToNodeStream is part of react-dom/server
	const htmlStream = renderToNodeStream(
		<ServerDataContext>
		    <App/>
		</ServerDataContext>
	);

	htmlStream.pipe(res, { end: false });
	htmlStream.on("end", () => {
		res.write(pagePart[2]);
		res.end();
	});
});
```

On client-side application must be wrapped in provider as well. A custom context provider is prepared for this job. It will read data from global variable (it was injected by this part of code: `res.write(data.toHtml())`).

```js
const BroswerDataContext = createBroswerContext();

hydrate(
	<BroswerDataContext>
	    <App />
	</BroswerDataContext>,
	document.getElementById("app")
);
```

That's it! What do you think about this approach? Is `useSSE` something you would use in your project?

Here are all resources:
*  [package on npm](https://www.npmjs.com/package/use-sse)
*  [project on GitHub](https://github.com/kmoskwiak/useSSE). 
* And  an example on [CodeSandbox](https://codesandbox.io/s/falling-waterfall-wnlwc?file=/README.md).