Creating a React Application with Headless Drupal 8

Creating a React Application with Headless Drupal 8

Submitted by Troy Kearney on Tue, 03/28/2017 - 15:43

Overview:

  • Setting up Drupal to enable RESTful Services

  • Implementing React with Drupal

    • Get a list of articles

    • Add a new article

For this tutorial I will go over how to setup a Drupal 8 site with RESTful services as well as setting up a simple React application which will retrieve a list of Articles and allow for the creation of articles for logged in users with the proper credentials.

Before working with React we will need to configure Drupal to provide us with some RESTful services.

The first step you will need to take is downloading and enabling the following modules: RESTful Web Services (rest), Serialization (serialization), and REST UI (restui) (https://www.drupal.org/project/restui). The Serialization module is required for RESTful  Web Services and the REST UI only provides a nice interface from which you can configure what pieces of content will be available via REST requests and which formats they are available.

Next head over to /admin/config/services/rest and select the actions you want to have available. In this tutorial I only want the Content or node/{node} resource enabled by both GET and POST requests. On the next page set the ‘Accepted request formats’ to JSON and the ‘Authentication providers’ to cookie. The ‘Authentication providers’ selection is required and at the moment we only have cookie available.

To be able to get all nodes on the front page, make a new display for the Frontpage view. The type of display should be REST export. In the Serialization settings set the Accepted request format to json. Give this new display a path of /node/rest

Let’s test out our GET service using curl before we move any further.

curl http://localhost/node/1?_format=json

This should return a JSON result of the node with the nid 1.
Before making a POST request, we will need to create a JSON file with some data to create a new node and obtain a X-CSRF-Token. To obtain the Drupal provided cookie, open up Chrome Dev Tools, select the Network tab, pick one of the loaded resources, select the Headers tab, and copy the cookie value under the Request Headers. Make a file called node.json and add the following JSON to the file.

{"type":[{"target_id":"article","target_type":"node_type"}],"title":[{"value":"Test Node     1"}],"body":[{"value":"Test Line 1\nTest Line 2.\n\n"}]}

For the X-CSRF-Token simply navigate to /rest/session/token and copy and save the string on that page. Now create your POST request as such:

curl -v -X POST -H 'X-CSRF-Token: <token>' -H 'Content-Type: application/json' -H ‘Cookie: <cookie>’ --data @/tmp/node.json  http://localhost/entity/node?_format=json

Note that the path /entity/node is used for 8.2.x. For 8.3.x /node can be used.

Now let’s get to the fun stuff!
On your server navigate to sites/<site name>/files and create a file called headless.html. Here is some boilerplate code to get us started.

<!DOCTYPE html>
<html>
 <head>
   <meta charset="UTF-8" />
   <title>Hello Drupal!</title>
 </head>
 <body>
   <div id="root">
       <h1>Hello Headless Drupal!</h1>
   </div>
 </body>
</html>

Navigate to http://localhost/sites/default/files/headless.html and you should be greeted with a “Hello Headless Drupal!” message.
Now  include React, React-DOM, and Babel from a CDN in the head.

<script src="https://unpkg.com/react@latest/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@latest/dist/react-dom.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>

And inside of a script tag with type=”text/babel”, we can add some basic React to make sure everything is in working order.

<script type=”text/babel”
   ReactDOM.render(
   <h1>Hello Drupal from React!</h1>,
   document.getElementById('root')
   );
</script>

Now you should see the message “Hello Drupal from React!”

To start making calls to Drupal’s RESTful services we will need to include a HTTP client. I am using axios in this tutorial. Include the below script in the head of your HTML.

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

Above the Hello Drupal from React message, add the following lines to perform an axios get request for the X-CSRF-Token. This code will confirm that axios is working properly and can be commented out until it is need later.

axios.get('/rest/session/token')
     .then(function (response){
       console.log(response);
       const csrf_token = response.data;
     })
     .catch(function (error){
       console.log(error);
     });

With all this working we can get started with building the React components.

We begin with an App component which will be the starting point for the entire application.

This will initialize the state with an empty array of nodes. We then populate the array in the componentWillMount() lifecycle method by making a get request for the nodes provided by the view at /node/rest created earlier.

In the render function we’re going to call the NodeList component which simply maps out each of the Nodes provided from App’s state.

//App Component
class App extends React.Component{
       constructor(props){
         super(props);
         this.state = {
           nodes: []
         }
       }
       componentWillMount(){
         let self = this; //Once inside the axios request, this will not be the same this.
         axios.get('/node/rest?_format=json')
         .then(function (response){
           let nodes = response.data;
           self.setState({
             nodes: nodes
           })
         })
         .catch(function(error){
           console.log(error);
         })
       }
       render (){
         return (
           <div>
       	<NodeList nodes={this.state.nodes} />
           </div>
         )
       }
     }

A simple NodeList component to map out each of the Nodes.

 // Node List Component
     let NodeList = ({ nodes }) => {
       return (
         <div>
           {nodes.map(n =>
             <Node node={n} key={Math.random()}/>
           )}
         </div>
       )
     }

The Node component is used as an instance of a node, which is being used above.

// Node Component
     let Node = ({ node }) =>{
       return (
         <div>
           <h4><a href={"/node/" + node.nid[0].value}>{node.title[0].value}</a></h4>
           <div>{node.body[0].value}</div>
         </div>
       )
     }

At the bottom of the file remember to render out the App component.

ReactDOM.render(
       <App />,
       document.getElementById('root')
     );

Now you should be able to visit http://localhost/sites/default/files/headless.html and you will see a list of node content. All that’s left now is to create a form component which will allow us to create a new Article node.

// Form Component
     class NewNodeForm extends React.Component{
       constructor(props){
         super(props);
         this.state = {
           title: '',
           body: '',
           csrfToken: ''
         }
         //Binds the handler functions for the change of the title,body, and submit handler.
         this.handleSubmit = this.handleSubmit.bind(this);
         this.handleTitleChange = this.handleTitleChange.bind(this);
         this.handleBodyChange = this.handleBodyChange.bind(this);
       }
//When the NewFormNode mounts to the DOM, make a request for the CSRF Token and saves the token to the Form’s state
       componentWillMount(){
         let self= this;          
         axios.get('/rest/session/token')
         .then(function (response){
           const csrf_token = response.data;
           self.setState({'csrfToken': csrf_token});
         })
         .catch(function (error){
           console.log(error);
         });
       }
       render(){
         return(
           <form onSubmit={this.handleSubmit}>
             <div>
               <label htmlFor="title">Title: </label>
               <input name="title" onChange={this.handleTitleChange} />
             </div>
             <div>
               <label htmlFor="body">Body: </label>
               <textarea name="body" onChange={this.handleBodyChange} />
             </div>
               <button> Submit </button>
           </form>
         )
       }
// update the state’s title property with the new text of the title
       handleTitleChange(e) {
         this.setState({title:e.target.value});
       }
// update the state’s body property with the new text of the body
       handleBodyChange(e) {
         this.setState({body:e.target.value});
       }
       handleSubmit(e) {
         e.preventDefault(); // to prevent the refresh of the page.
         let self = this.state;
        // create the object of the data. At the moment this is hard coded to make a content type of article
         let node = {
           "type":[{"target_id":"article","target_type":"node_type"}],
           "title":[{"value": self.title}],
           "body":[{"value": self.body}]
         }
        // setting up the headers with the csrfToken in the state.
         let config = {
           headers: {'X-CSRF-Token': self.csrfToken}
         }
         axios.post('/entity/node?_format=json',node,config)
         .then(function(success){
           console.log(success);
         })
         .catch(function(error){
           console.log(error)
         });
       }
     }

There you have it! A simple approach to decoupling Drupal with React.

The full code for this tutorial is available here