Using React’s Context to Pass Variables From the Server to the Client
I recently got hired by I client to create a new functionality for a single page application. The client wanted the marketing team of the company to be able to easily change the landing page content. The landing page was already built with React
using isomorphic/universal rendering and the ability to edit and save the page content was also already in place and saved in a database.
The problem I had to solve was how to pass variables (containing the data necessary for rendering the page) from the server to the client in an application using react-router without losing the benefits of isomorphic rendering. Initially, this seemed to me as a very easy task, but as I will later explain, the fact that the application was built with isomorphic rendering made this task an opportunity to learn something new.
The goal of this blog post is to explain how I used React’s context feature to pass variables from the server to the client without losing the benefits of isomorphic rendering.
The Challenge
I found a couple of solutions to similar problems online, but none of them worked for the same reason: the HTML rendered by the server wasn’t the same as the one rendered by the client. In an isomorphic application, the HTML rendered by the server must be the same as the one rendered by the client, otherwise you would lose the benefits of it and get a React check-sum warning.
The Solution
I had already worked with the well known state container Redux
, and I figured I could use something similar to the react-redux
Provider component. So I went ahead and took a look at the source code and found out this component internally uses React's context feature (unknown to me at this time) to make the state of the application seemingly available to all container components.
React’s context feature allows to pass data through the component tree without having to pass the props down manually at every level. It’s actually similar to using global variables to pass state through the application, thus it must be used sparingly (also its API is an experimental feature which is likely to change in the future).
Let me now explain all the components I used to implement the desired feature. I hope this gives you a better understanding of how I was able to use React’s context feature to solve the problem described above:
Provider
: This component implements the context feature, and exposes theprop
calledappData
to other components. The whole application is going to be wrapped by it:
server.jsx
: This file has anExpress
handler to catch all routes and isomorphically serve the application using React'srenderToString
method. NoticeappData
is passed to both theProvider
and theindex
template:
index.ejs
: This file bootstraps the whole application. It receivesappData
and then initializes the application:
client.jsx
: This file starts the application like you would normally do in the client side. It is wrapped by theProvider
component and passes to it theappData
received from theindex.ejs
file:
App.jsx
(optional): TheApp
component is the top level component of the application and all other components are wrapped by it as such (usingreact-router
):
<Route component={App}>
<Route path="/" component={HomeContainer}/>
</Route>
This component is optional because appData
is already available to all components of the application through React's context
. But as mentioned above, context
must be used sparingly, so I decided to map the context
as props
for all container components instead since this is the common approach when using React:
Now all container components such as HomeContainer
(see /
route above) can access appData
through props
as you would normally do in React.
Implementing React’s context feature was a fun learning experience and I hope this post serves as a guide of how to do so. Have you ever had to solve a similar issue? How did you manage to do it?