Author avatar

Gaurav Singhal

How to Manage the Entire Body in React.js

Gaurav Singhal

  • May 19, 2020
  • 6 Min read
  • 259 Views
  • May 19, 2020
  • 6 Min read
  • 259 Views
Web Development
Front End Web Development
Client-side Framework
React

Introduction

React is a vast library that abstracts all the complexities of the DOM. It uses the virtual DOM concept, which is much faster than direct DOM manipulation for large and nested elements. Each React component represents a node in the DOM. By design, components do not talk up the component hierarchy directly; you have to pass the data using callback props.

But what if there's a DOM node that your React app wants to reach and modify something it doesn't control? In this short guide, you'll learn how to manage the entire body, including the <body> element in React.

Using `ReactDOM.render()` Method

The react-dom library provides DOM-specific utilities. The ReactDOM.render() method renders a React component into the DOM in the specified DOM element. It takes in a React element as the first parameter and a DOM node as the second. It also takes an optional callback as a third parameter, which is executed when the component has finished rendering.

1ReactDOM.render(component, DOMnode [, callback]);
js

You can render a component in the body as follows.:

1const App = () => (
2  <div>
3    <h1>Hello World</h1>
4  </div>
5);
6
7ReactDOM.render(<App />, document.body);
js

This is a possible solution, but it is considered bad practice to render a component to document.body. That is because any other third party library or browser extension can manipulate document.body and its children, which may affect the React component rendered inside of it.

You could do document.body.appendChild(document.createElement("div")), but this will render the component inside of a direct child of the body.

Higher-Order Component to Manipulate the `<body />` Element

In this section, you'll look into a more "React" way of handling the <body> element.

Developers often forget that they can use Browser DOM API inside of a React component. When a React component manipulates the DOM node directly, it is considered as a side effect. Nevertheless, it's perfectly valid.

So to add attributes or classes to the <body> element, you can do:

1// to set new class
2document.body.classList.add(className);
3
4// to set attributes
5document.body.setAttribute(attrName, value);
js

The above methods are specified by the DOM API to manipulate DOM nodes, and are not a part of core React or framework agnostic.

Using the above methods, create a Higher Order Component (HOC) to add classes and set data attributes in the <body> element.

1import React, { Component } from "react";
2
3class ReactBody extends Component {
4  render() {
5    return props.children;
6  }
7}
jsx

The ReactBody component will take in the classes and attributes as props. The following is the propTypes definition for the component:

1import PropTypes from "prop-types";
2
3ReactBody.propTypes = {
4  classes: PropTypes.array,
5  dataAttrs: PropTypes.object,
6  children: PropTypes.node.isRequired
7};
jsx

Next, inside the componentDidMount() lifecycle method, loop through each of the class names and attributes, and add them to the <body> element.

1// ...
2componentDidMount() {
3    if (props.classes) {
4        props.classes.forEach(class => {
5            document.body.classList.add(class);
6        })
7    }
8    else if (props.dataAttrs) {
9      Object.keys(props.dataAttrs).forEach(attr => {
10        document.body.setAttribute(`data-${attr}`, props.dataAttrs[attr]);
11      });
12    }
13  }
14// ...
jsx

Don't forget to remove the attributes and class name after the component gets unmounted from DOM.

1// ...
2componentWillUnmount() {
3    if (props.classes) {
4        props.classes.forEach(class => {
5            document.body.classList.remove(class);
6        })
7    }
8    else if (props.dataAttrs) {
9      Object.keys(props.dataAttrs).forEach(attr => {
10        document.body.removeAttribute(`data-${attr}`, props.dataAttrs[attr]);
11      });
12    }
13  }
14// ...
jsx

Finally, your Higher-Order Component should look as follows:

1import React, { Component } from "react";
2import PropTypes from "prop-types";
3
4class ReactBody extends Component {
5  componentDidMount() {
6    if (props.classes) {
7        props.classes.forEach(class => {
8            document.body.classList.add(class);
9        })
10    }
11    else if (props.dataAttrs) {
12      Object.keys(props.dataAttrs).forEach(attr => {
13        document.body.setAttribute(`data-${attr}`, props.dataAttrs[attr]);
14      });
15    }
16  }
17
18  componentWillUnmount() {
19      if (props.classes) {
20        props.classes.forEach(class => {
21            document.body.classList.remove(class);
22        })
23    }
24    else if (props.dataAttrs) {
25      Object.keys(props.dataAttrs).forEach(attr => {
26        document.body.removeAttribute(`data-${attr}`, props.dataAttrs[attr]);
27      });
28    }
29  }
30
31  render() {
32      return this.props.children
33  }
34}
35
36ReactBody.propTypes = {
37  classes: PropTypes.array,
38  dataAttrs: PropTypes.object,
39  children: PropTypes.node.isRequired
40};
41
42export default ReactBody;
jsx

Once you have your ReactBody HOC ready, you can wrap other components inside of <ReactBody /> as shown below.

1// ...
2const Modal = props => {
3  <ReactBody classes={props.isOpen && ["open-modal"]}>
4    <div className="modal">{/* ... */}</div>
5  </ReactBody>;
6};
jsx

Conclusion

It's highly recommended that you never render your React app directly inside the <body> element, as it can lead to unexpected behaviors. A better way to manipulate the <body> is to use DOM methods like classList.add, setAttribute, etc. To reuse these methods across React components, you should create a HOC that does all the manipulation to the document body. If you still have any questions, contact me at CodeAlphabet.