Entity view (Content)

Simple website approach using a Headless CMS: Part 3

By paulo
Feb. 22, 2018

This is the last post of my quick Headless journey, in the first one I approached the concept of Headless and presented some solutions like Drupal, GraphCMS, Contentful and Cockpit CMS. In the second one, I extended those concepts by detailing Cockpit CMS, mostly due to the simplicity of the admin interface and the way that we deal with common operations like creating a content type and manage his contents.

This post will culminate with the last but not less important piece, the frontend layer, where we interact with the CMS API to retrieve or send contents.

As explained in the first post, when using a Headless approach we are not anymore restricted by the CMS in terms of presentation, we can deal with it as we want, our only compromise is on following the API data contract, usually in a form of JSON structured data.

So to implement a website quickly and at the same time dynamic, modern, robust, and easy to maintain I suggest React JS as it provides all the mechanisms that we need, and by doing a very simple website, components based, I hope to convince you.

The below diagram illustrates how the contents are organized in Cockpit and React:

For each Cockpit collection, I have a corresponding React component, the Basic Page collection corresponds to a Basic Page component that is responsible for handling the navigation pages. All other collections have also a corresponding React component.

 

 

Above screenshot illustrates my collections on Cockpit and they are corresponding on React to their counterparts:

 

On React when the App is initialized, I fetch the Basic Pager collections using the /api/collections/get/basicpage?token=xxtokenxx endpoint.

componentDidMount() {
   if (this.props.pages.length === 0) {
     this.props.fetchPages.fetchCollection();
   }
 }

The fetchCollection() is defined in the actions:

export function fetchCollection(collection) {
 return dispatch => {
   return fetch(getCollectionUrl('basicpage'), {
     method: 'post',
     mode: 'cors',
     body: JSON.stringify({
       filter: { published: true },
       populate: 1,

       simple: 1,
       limit: 50,
     }),
     headers: {
       'Content-Type': 'application/json',
     },
   })
     .then(response => response.json())
     .then(json => dispatch(parsePage(json)));
 };
}
 

The fetchCollection() will set the pages as an array of objects, where each item corresponds to a Basic Page collection, that structure is exemplified as below:

 

Having all the basic pages it’s enough to build the navigation menu. I have an attribute on the basic page that defines if it should be displayed on the main menu or not:

 

The navigation menu links are created and they will be used on the NavigationBar component that will display them as links. The pages are passed down to a component that will build the React Routes:

class App extends Component {
 componentDidMount() {
   if (this.props.pages.length === 0) {
     this.props.fetchPages.fetchCollection();
   }
 }

 render() {
   const pages = this.props.pages;
   const links = [];

   pages.map(
     (entry, idx) =>
       entry.menu &&
       links.push({
         title: entry.title,
         content: entry.body,
         path: (idx === 0 && '/') || `/${entry.title_slug}`,
       })
   );

   return (
     <ConnectedRouter history={history}>
       <div className="App">
         <Grid fluid>
           <Row>
             <Col xs={12}>
               <NavigationBar
                 title="Cockpit React Example"
                 links={links}
               />
             </Col>
           </Row>
           <Row>
             <Main pages={pages} />
           </Row>
         </Grid>
       </div>
     </ConnectedRouter>
   );
 }
}


So for each received page I only need to inspect the components field (Multiple Link Collection), that field includes the components for that page and for each one I have a mapping that will dynamically include the React component:

import React from 'react';
import PropTypes from 'prop-types';

const componentsConfig = [
 {
   name: 'banner',
   component: require('../../components/Banner').default
 },
 {
   name: 'faq',
   component: require('../../components/Faq').default
 },
 // (...)
];

let dynamicComponents = {};
for (var i = componentsConfig.length - 1; i >= 0; i--) {
 dynamicComponents[componentsConfig[i].name] = componentsConfig[i].component;
}

const Components = props => {
 return (
   <div>
     {props.components.map((collection, idx) => {
         if (dynamicComponents.hasOwnProperty(collection._link)) {
           let Component = dynamicComponents[collection._link];
           return (
             <div key={`${collection._link}-${idx}`}>
               <Component {...collection} />
             </div>
           );
         }
       })}
   </div>
 );
};

Components.propTypes = {
 components: PropTypes.array
};

export default Components;


My BasicPage component just needs to make use of the above Components React component passing as a prop the array of Linked Collections:

import React from 'react';
import PropTypes from 'prop-types';
import Components from './Components';

const BasicPage = props => {
 return (
   <div className="components">
     <Components components={props.data.components} />
   </div>
 );
};

BasicPage.propTypes = {
 idx: PropTypes.number,
 data: PropTypes.object,
};

export default BasicPage;

This seems to result really simple and nice and fulfill my needs of having the Cockpit collections being rendered as React components. Extending it a bit further with some libraries like:
 

  • Prettier - Code formatter that enforces a consistent style by parsing your code and re-printing it with its own rules that take the maximum line length into account, wrapping code when necessary. In the example code that I provide I have a git pre-commit hook that runs prettier every time a commit is performed doing auto-formatting of the code. There are also Prettier addons for editors like VSCode or Sublime Text.

  • Redux - Predictable state container for any Javascript view library like React.

  • History - Minimal API that lets you manage the history stack, navigate, confirm navigation, and persist state between sessions.

  • React Router - React Router is a collection of navigational components that compose declaratively with your application.

  • Material UI - Library that provides React components that implement Google's Material Design (http://www.material-ui.com).

  • Flexbox Grid - A set of React components that implement flexboxgrid.css (http://flexboxgrid.com)

  • React Slick - React port of the slick carousel plugin (http://kenwheeler.github.io/slick/).

  • React Markdown - Render Markdown text as React components (https://github.com/rexxars/react-markdown).

...and we have everything that we need to render the components in a responsive and modern layout as you can observe in the following screencast:

https://youtu.be/lGzIJKcp-d4

 

<iframe width="560" height="315" src="https://www.youtube.com/embed/lGzIJKcp-d4" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>

The final example website can be accessed at  https://pauloamgomes.github.io/cockpit-react-example/

Project code can be accessed at https://github.com/pauloamgomes/cockpit-react-example

Conclusions

My main purpose with above implementation was to explain the simplicity and flexibility we have when handling a Headless CMS in combination with a Javascript library like React JS. However, and as you can understand, in the real world things may be not so simple as I described, and some further work may be required, but that is what we face everyday, understand the needs and make all the things pretty and functional, and at same time having some fun on the work we do.