react-ssr-fork
v1.0.1
Published
React SSR Fork facilitates rendering different content on server and client side.
Maintainers
Readme
React SSR Fork
React SSR Fork facilitates rendering different content on server and client side, and solves hydration mismatch issue.
Features
- 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.
Installation
Install the library:
npm install --save react-ssr-forkUsage
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>.
Requirements
- On both client and server sides
<Client>and<Server>must to be wrapped inside<ForkProvider> - Requires React 16.3 and above
Why not use this.state.isCient approach?
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 server' : 'I run on client'}
</div>
);
}
componentDidMount() {
this.setState({ isClient: true });
}
}
ReactDOM.hydrate(
<Message />,
document.getElementById('root')
);On client side during initial render (hydration) this.state.isClient is false. Thus <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 when this is not necessary.
Unit testing
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);
});
});License
Licensed under MIT
