Understanding ReactJS — data hydration / initialization

At times you might want to have some information available to your application from outside sources — maybe you’re embedding your application in some bigger website and want to access data that is already present (for example data of authorized user).

Passing data to the application at run time is known as “initialization”, “bootstrapping” or “state hydration”. Lets go through couple of solutions.

Important:

Please note that allowing data from outside of your application to be accessed inside of React might open you to attacks such as XSS or CSRF. Please make sure all the data you are accessing in React comes from a trusted source.
While you do not need to use any special functions to escape characters, you should be very careful if you will be using dangerouslySetInnerHtml.

A — make a API call

The first solution assumes the data you are looking for is available by calling a API, which might be ideal when the response you expect will be the same for all of your users.

In order to get the data you can make an AJAX call (for example by using axios or fetch).

Pros:

  • secure — very unlikely that an attacker would be able to inject malicious code

Cons:

  • performs an additional HTTP request which adds to load time
  • unless already authorized, can’t be used to fetch personalized response; authorization adds the overhead of yet another request

B — pass data via window object

When the data is already available to the backend (for example the user is already authorized, or set of images to display in your React widget is known) you can assign the data to window object and then read it in React application.

First lets make changes to our application:

class User extends React.Component {
contructor() {
super();
if(window.reactApp) {
this.state = { user: window.reactApp.user };
}
else this.state = { user: undefined };
}
}

Now we can embed the application:

<div id="react"></div>
<script>
window.reactApp = {
user: {
name: 'BTM',
email: 'example@example.com'
}
}
</script>
<script src="react-application.js"></script>

Pros:

  • data provided to React can be tailored to authorized user or generic on the backend side
  • no additional requests needed

Cons:

  • our application relies on the data provided from outside of React context, making it less reusable
  • possibility of attackers manipulating the data via XSS/CSRF
  • in case we want to embed multiple applications on the same page we would need to make each application use different window.reactApp variable if need to have different input data

C — pass data via attribute on parent node

This is basically a modified version of the example described above, but instead of storing the data in window global object, we are going to store it as a data-attribute on the node into which React will render.

This time let us start by changing the HTML:

<div id="react" data-react='{"user":{"name":"BTM","email":"example@example.com"}}'></div>
<script src="react-application.js"></script>

Now we can change our application:

const node = document.getElementById('root');
const userData = node.dataset.react ? JSON.parse(node.dataset.react) : {};
ReactDOM.render(<App user={react.user} />, node);

Pros:

  • data provided to React can be tailored to authorized user or generic on the backend side
  • no additional requests needed
  • we’re no longer polluting the window object

Cons:

  • our application relies on the data provided from outside of React context, making it less reusable
  • possibility of attackers manipulating the data via XSS/CSRF
  • data needs to be provided as JSON string, which can lead to some issues in both security and data consistency

D — exposing the render function to HTML

The solutions provided before might very well be enough for most use case, but sometimes you want to be able to not only pass some data at run time but also be able to update it at later time or manually trigger the rendering of your application. In order to do just that we can provide our HTML page access to the rendering mechanism.

First thing we need to do is create a function that will init our application and expose it via window object (the function on itself would not be available outside of React context due to how Webpack works):

function runApplication(data, node) {
ReactDOM.render(<App data={data} />, node);
}
window.runReactApplication = runApplication;

Now we can call our application at any given time:

<div id="MyApp"></div>
<script src="react-application.js"></script>
<script>
window.runReactApplication({
user: { name: 'BTM', email: 'example@example.com' }
}, document.querySelector('#MyApp'));
</script>

Now, anytime we call the window.runReactApplication function with new data as first parameter (but the same node as the second one!) our <App /> component will receive new props, just like a normal React life-cycle would do it.

Pros:

  • data provided to React can be tailored to authorized user or generic on the backend side
  • no additional requests needed
  • data can be updated and app rendered whenever it’s needed

Cons:

  • app needs to be initialized from the HTML
  • possibility of attackers manipulating the data via XSS/CSRF

Conclusion

As with all things React — there is no one, definitive answer to the question “how to pass initial data to my application” — you need to pick a solution that works best for your use case.