React SSR Fork facilitates rendering different content on server and client side, and solves hydration mismatch issue.
- Provides
<Client>
component to render client side content, and<Server>
for server side - Automatically solves the hydration issue of mismatched client and server side content
- Prevents unecessary client side re-renders when solving hydration mismatch
- Requires minimum setup and is easy to use.
Install the library:
npm install --save react-ssr-fork
Wrap your main application component inside <ForkProvider>
. This component contains the business logic of solving hydration mismatch issue, also provides context for <Client>
and <Server>
components.
- Your client side entry might look like this (
<ForkProvider>
wrapping is obligatory):
// client.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { ForkProvider } from 'react-ssr-fork';
import App from 'your-components/App';
ReactDOM.hydrate(
<ForkProvider>
<App />
</ForkProvider>,
document.getElementById('root')
);
- And your server side entry file could look this way (
<ForkProvider>
wrapping is obligatory):
// server.jsx
import React from 'react';
import { renderToString } from 'react-dom/server';
import { ForkProvider } from 'react-ssr-fork';
import App from 'your-components/App.jsx';
const content = renderToString(
<ForkProvider>
<App />
</ForkProvider>
);
// ...
- Then use
<Client>
component to render specifically client side content, and respectively<Server>
to render server side content. Your<App>
component might look like this:
// App.jsx
import React from 'react';
import { Client, Server } from 'react-ssr-fork';
export default function App() {
return (
<div>
<Client>
I run on client
</Client>
<Server>
I run on server
</Server>
</div>
);
}
Here's what happens when you run the application:
- On server side
<App>
simply renders<div>I run on server</div>
- On client side during hydration
<App>
renders<div>I run on server</div>
(to match the content sent from server), then right away re-renders to actual client content<div>I run on client</div>
.
- On both client and server sides
<Client>
and<Server>
must to be wrapped inside<ForkProvider>
- Requires React 16.3 and above
ReactDOM.hydrate()
documentation suggests solving hydration mismatch warning using two-pass rendering and this.state.isClient
flag. Here's a simple imlementation:
import React, { Component } from 'react';
class Message extends Component {
state = { isClient: false }
render() {
return (
<div>
{this.state.isClient ? 'I run on client' : 'I run on server'}
</div>
);
}
componentDidMount() {
this.setState({ isClient: true });
}
}
ReactDOM.hydrate(
<Message />,
document.getElementById('root')
);
On server side <Message>
renders <div>I run on server</div>
.
On client side during initial render (hydration) this.state.isClient
is false
, so <Message>
outputs <div>I run on server</div>
. This output matches the one sent from server side, and React doesn't throw any mismatch warnings during hydration.
Right away after mounting of <Message>
, componentDidMount()
method is invoked and this.state.isClient
becomes true
. As result <Message>
renders <div>I run on client</div>
.
As described above, solving hydration mismatch using two-pass rendering works.
But there is a drawback. If a new instance of <Message>
is rendered on the client side on later stages, two-pass rendering will be applied even if that's unnecessary. This makes your component slower. That's the problem solved by React SSR Fork library, which prevents unnecessary two-pass rendering.
React SSR Fork components faciliate unit testing of your components.
<ForkProvider>
accepts a special boolean prop canUseDom
meant to indicate manually the client or server environment in your unit tests. For example:
import React from 'react';
import { mount } from 'enzyme';
import { ForkProvider } from 'react-ssr-fork';
function App() {
return (
<div>
<Client>
I run on client
</Client>
<Server>
I run on server
</Server>
</div>
);
}
describe('<App>', function () {
test('renders client content on client side', function () {
const wrapper = mount(
// Client environment: canUseDom=true
<ForkProvider canUseDom={true}>
<App />
</ForkProvider>
);
expect(wrapper.contains('I run on client')).toBe(true);
expect(wrapper.contains('I run on server')).toBe(false);
});
test('renders server content on server side', function () {
const wrapper = mount(
// Server environment: canUseDom=false
<ForkProvider canUseDom={false}>
<App />
</ForkProvider>
);
expect(wrapper.contains('I run on client')).toBe(false);
expect(wrapper.contains('I run on server')).toBe(true);
});
});
Licensed under MIT