In this tutorial series we will demonstrate how to build a simple blog engine with Carbon LDP as our data layer and React components for the the user interface.

This tutorial is largely aimed at people who want to start exploring Carbon and React, who have some background in JavaScript and CSS, and who are comfortable with having nodejs/npm installed on their machines. You needn’t have any previous experience with Carbon or React; this will be a beginner’s level tutorial. We’ll also be using Create React App, which is a comfortable environment for learning React, and is a great way to start building a single-page application in React.

Complimentary source code for this tutorial can be found in the GitHub repo, carbonldp-react-blog.

In today’s tutorial (part 1), we will focus only on setting up the front-end with React, but before we get started, let’s take a moment to review the end goal – what we plan on building over the course of the series.

The end goal

Since this is just a tutorial, our blog engine will not be as robust or feature rich as something like, say, WordPress, but it will be enough to teach you the basics of using React with Carbon LDP. Following is a preview of what we’ll be building.

The blog entry form

The blog entry form

The blog post list view

The blog post list view

The blog post detail view

The blog post detail view

Beginning with the end in mind, our simple blog engine will include:

  • A top-bar and a mobile-friendly menu drawer using React Material-UI components
  • An empty home view (where some sort of landing page or dashboard could be implemented later)
  • An editor view with a form for submitting new blog posts (no WYSIWYG; we’ll just keep it simple for demonstration purposes)
  • A blog post list view that presents a list of recent blog posts, each linking to…
  • A blog detail view for reading each post

When this three-part series is complete, you will have received a beginner’s introduction to both Carbon and React and you’ll have the foundation for a blog engine that you can extend and enhance. Perhaps you’ll make the world’s best new blog engine powered by Linked Data! Let’s begin.

Create the project skeleton using Create React App

Create React App sets up your development environment so that you can use the latest JavaScript features, provides a nice developer experience, and optimizes your app for production. You’ll need to have Node >= 8 and npm >= 5.2 on your machine.

To create the project, run:

npx create-react-app carbonldp-react-blog
cd carbonldp-react-blog
npm start

Note : npx on the first line is not a typo — it’s a package runner tool that comes with npm 5.2+. By using the npx command we’re able to execute create-react-app without the need to download and install it first.

This should open your browser to http://localhost:3000/ and display the following…

Create React App starting point

Create React App staring point

Create React App doesn’t handle backend logic or databases; it just creates a front-end build pipeline, so you can use it with any backend you want. Under the hood, it uses Babel and webpack, but you don’t need to know anything about them.

When you’re ready to deploy to production, running npm run build will create an optimized build of your app in the build folder. You can learn more about Create React App from its README and the User Guide.

Be sure to take a look at README.md in the newly created project. Create React App delivers one of the most comprehensive README file’s I’ve ever seen; it’s chock-full of good info!

If you look at the project at this point, you’ll see that Create React App created the following application structure…

carbonldp-react-blog
├── node_modules
├── public
│   ├── favicon.ico
│   ├── index.html
│   └── manifest.json
├── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    └── serviceWorker.js
├── .gitignore
├── package-lock.json
├── package.json
└── README.md

public/index.html is the main HTML file that includes your React code and application and provides a context for React to render to. If you take a look at the source code for that file, you’ll find <div id="root"> in there; it’s the important part where your react application gets rendered in your browser.

The important directory for us as developers is the src/ directory where we will store all of our modifiable code.

index.js stores our main Render call from ReactDOM (more on that later). It imports our App.js component that we start off with and tells React where to render it (remember that div with an id of root?). index.css stores the base styling for our application.

App.js is a sample React component called “App” that we get for free when creating a new app. We’ll actually be deleting the entire contents of the file and starting over! App.css stores styling for that component specifically. Finally, logo.svg is just the React logo.

App.test.js is our first set of tests to run against our sample App component that we start off with.

Install Material UI

In order to focus on functionality, let’s leave most of the UI up to a proven set of components. We’ll use
Material-UI, which is a collection of React components that implement Google’s Material Design. We’ve chosen it for this tutorial series as it’s one of the most popular React component libraries available. To install it, run the following command:

npm install @material-ui/core

Roboto Font

The Roboto font will not be automatically loaded by Material-UI and this is the common font for use with Material UI. We can load it from a CDN or install it as part of our build pipeline. To keep things simple for now, we’ll just get it from CDN. Add the following line before the closing </head> tag in public/index.html:

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">

Font Icons

In order to use the font Icon component you must also add the Material icons font. Add the following line before the closing </head> tag in public/index.html:

<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">

SVG Icons

In order to use prebuilt SVG Material icons, such as those found in the component demos, we must install the @material-ui/icons package:

npm install @material-ui/icons

A minor thing

Also, in public/index.html swap out the existing meta viewport line for this one:

<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"/>

Now, let’s just do a quick acid test. Modify src/App.js to be as follows.

import React, { Component } from 'react';
import Button from '@material-ui/core/Button';
import './App.css';

class App extends Component {
  render() {
    return (
      <Button variant="contained" color="primary">
      Hello World
      </Button>
    );
  }
}

export default App;

We’ve used the Button component from Material-UI, so the rendered result should now look like this when you run the app using npm start:

Material UI acid test

Note that you can now delete src/logo.svg since we’re not using it anymore.

Modify src/index.js to be as follows:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { MuiThemeProvider, createMuiTheme} from '@material-ui/core'

const theme = createMuiTheme({
    typography: {
        useNextVariants: true,
    },
});

ReactDOM.render(
    <MuiThemeProvider theme={theme}>
        <App />
    </MuiThemeProvider>,
    document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();

That just sets up the app to take advantage of the Material-UI theming system. We will not be doing much theme work in this tutorial, but it will prepare your app for such work in the future. Also, if we do not specify useNextVariants: true, in the theme object, as we’ve done above, we may get a pesky warning in the console related to deprecated typography variants.

Throughout this tutorial series, we’ll be committing at stable checkpoints, which you can find in the Git history for this project’s repo. At this point, we’ll commit with the following message:

Commit: Add Material-UI

Implement responsive drawer

I love the responsive drawer component because it provides a way to have navigation that works well on both desktop and mobile. In this section, we’ll swipe code from the Material-UI responsive drawer demo and tailor it for our own needs. We’ll start by creating a new custom component called Layout.

Create a new directory, src/components and within that, a directory called layout/ (i.e. src/components/layout).

In the layout/ directory, create an empty index.js file.

Now we’re going to paste a bunch of code and we’re not going to pause to go over everything that’s going on with that code. It is just a slightly modified version of the demo code given for the responsive drawer. Don’t worry about understanding how it works for now.

Paste the following code into src/layout/index.js:

import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import {
    AppBar, CssBaseline, Divider, Drawer, Hidden, IconButton, List, ListItem, ListItemIcon, ListItemText, Toolbar, Typography
} from '@material-ui/core';

import MenuIcon from '@material-ui/icons/Menu';
import HomeIcon from '@material-ui/icons/Home';
import ListIcon from '@material-ui/icons/List';

const drawerWidth = 240;

const styles = theme => ({
    root: {
        flexGrow: 1,
        zIndex: 1,
        overflow: 'hidden',
        position: 'relative',
        display: 'flex',
        width: '100%',
    },
    appBar: {
        zIndex: theme.zIndex.drawer + 1,
    },
    navIconHide: {
        [theme.breakpoints.up('md')]: {
            display: 'none',
        },
    },
    toolbar: theme.mixins.toolbar,
    drawerPaper: {
        width: drawerWidth,
        [theme.breakpoints.up('md')]: {
            position: 'relative',
        },
    },
    content: {
        flexGrow: 1,
        backgroundColor: theme.palette.background.default,
        padding: theme.spacing.unit * 3,
    },
});

class Layout extends React.Component {
    state = {
        mobileOpen: false,
    };

    handleDrawerToggle = () => {
        this.setState(state => ({ mobileOpen: !state.mobileOpen }));
    };

    render() {
        const { children, classes, theme } = this.props;

        const drawer = (
            <div>
                <div className={classes.toolbar} />
                <Divider />
                <List component="nav">
                    <ListItem button>
                        <ListItemIcon>
                            <HomeIcon />
                        </ListItemIcon>
                        <ListItemText primary="Home" />
                    </ListItem>
                    <ListItem button>
                        <ListItemIcon>
                            <ListIcon />
                        </ListItemIcon>
                        <ListItemText primary="Blog" />
                    </ListItem>
                </List>
            </div>
        );

        return (
            <Fragment>
                <CssBaseline/>
                <div className={classes.root}>
                    <AppBar className={classes.appBar} position="absolute">
                        <Toolbar>
                            <IconButton
                                color="inherit"
                                aria-label="Open drawer"
                                onClick={this.handleDrawerToggle}
                                className={classes.navIconHide}
                            >
                                <MenuIcon />
                            </IconButton>
                            <Typography variant="h6" color="inherit" noWrap>
                                Carbon LDP / React Blog Engine
            </Typography>
                        </Toolbar>
                    </AppBar>
                    <Hidden mdUp>
                        <Drawer
                            variant="temporary"
                            anchor={theme.direction === 'rtl' ? 'right' : 'left'}
                            open={this.state.mobileOpen}
                            onClose={this.handleDrawerToggle}
                            classes={{
                                paper: classes.drawerPaper,
                            }}
                            ModalProps={{
                                keepMounted: true, // Better open performance on mobile.
                            }}
                        >
                            {drawer}
                        </Drawer>
                    </Hidden>
                    <Hidden smDown implementation="css">
                        <Drawer
                            variant="permanent"
                            open
                            classes={{
                                paper: classes.drawerPaper,
                            }}
                        >
                            {drawer}
                        </Drawer>
                    </Hidden>
                    <main className={classes.content}>
                        <div className={classes.toolbar} />
                        {children}
                    </main>
                </div>
            </Fragment>
        );
    }
}

Layout.propTypes = {
    classes: PropTypes.object.isRequired,
    theme: PropTypes.object.isRequired,
};

export default withStyles(styles, { withTheme: true })(Layout);

One thing that’s notable about this code, is the {children} placeholder within the <main> tags. The children property comes from this.props and it’s going to contain whatever is passed in between the <Layout> tags when we us that component.

So, let’s use the Layout component. Modify src/App.js so that it imports and uses our new component as follows:

import React, { Component } from 'react';
import './App.css';
import Layout from './components/layout';

class App extends Component {
  render() {
    return (
      <div className="App">
        <Layout/>
      </div>
    );
  }
}

export default App;

Now, you should see the following result in your web browser.

Responsive drawer - rendered

In keeping with our practice of committing at stable checkpoints, we’ll commit now with the following message:

Commit: Implement responsive drawer

Implement navigation routes

Now that we have a layout and a menu, we can implement some router navigation. However, before implementing navigation, we need some components that we can actually navigate to. For this, we’ll create three new simple components for the Home view and Blog view with simple output for an acid testing. Create the necessary directories and components as follows:

Create: src/components/blog/index.js

import React, { Component } from 'react';
class Blog extends Component {
  render() {
    return (
      <div className="Blog">
        <h1>Blog</h1>
      </div>
    );
  }
}

export default Blog;

Create: src/components/home/index.js

import React, { Component } from 'react';
class Home extends Component {
  render() {
    return (
      <div className="Home">
        <h1>Home</h1>
      </div>
    );
  }
}

export default Home;

Now execute the following command to install react-router-dom:

npm install --save react-router-dom

Modify src/App.js as follows:

import React, { Component } from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom'
import Home from './components/home';
import Blog from './components/blog';
import Layout from './components/layout';
import './App.css';

class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <Layout>
          <Switch>
            <Route exact path='/' component={Home} />
            <Route exact path='/blog' component={Blog}/>
          </Switch>
        </Layout>
      </BrowserRouter>
    );
  }
}

export default App;

At this point, when you run the app with npm start, you will land on Home by default since that’s now tied to the path /. You can also manually enter the path /blog in your browser address bar to see that the router is working.

Blog view rendered

In keeping with our practice of committing at stable checkpoints, we can commit here with the following message:

Commit: Implement navigation routes

Link the menu items

Next, we need to add links to the menu items so that we can navigate by clicking them.

We’ll use the <NavLink> tag, which is a special version of the <Link> that can add styling attributes to the rendered element when it matches the current URL.

In src/layout/index.js, we need to import the Link component as follows:

import { NavLink } from 'react-router-dom';

Also, in src/components/layout/index.js we can change the drawer constant to be as follows:

const drawer = (
    <div>
        <Hidden smDown>
            <div className={classes.toolbar} />
        </Hidden>
        <Divider />
        <List component="nav">
            <ListItem component={NavLink} to="/" button>
                <ListItemIcon>
                    <HomeIcon />
                </ListItemIcon>
                <ListItemText primary="Home" />
            </ListItem>
            <ListItem component={NavLink} to="/blog" button>
                <ListItemIcon>
                    <ListIcon />
                </ListItemIcon>
                <ListItemText primary="Blog" />
            </ListItem>
        </List>
    </div>
);

Now, our menu items are actually navigable. When you click on the menu items on the left, the associated views are loaded at right.

Committing at stable checkpoints, we’ll commit with the following message:

Commit: Link menu items

Create the New Post form view

Now we’ll implement a new view that allows us to create new blog posts, which will be persisted to Carbon LDP.

Create the following component: src/components/editor/index.js with the following contents (for now):

import React, {Component} from 'react'
import {withStyles} from '@material-ui/core/styles'
import {Button, TextField} from '@material-ui/core'

const styles = theme => ({
    FormControl: {
        width: 500
    },
    xsFormControl: {
        width: 100
    }
});

class Editor extends Component {

    state = {
        post: {
            $id: '',
            title: '',
            slug: '',
            excerpt: '',
            body: ''
        }
    };

    handleChange = name => ({target: {value}}) => {
        this.setState({
            post: {
                ...this.state.post,
                [name]: value
            }
        })
        if (name === 'title') {
            let friendlySlug = this.makeFriendlySlug(value)
            this.setState(() => ({
                post: {
                    title: value,
                    slug: friendlySlug
                }
            }))
        }
    }

    /**
     * Takes a given string and makes it URL friendly. It ignores nonalphanumeric characters,
     * replaces spaces with hyphens, and makes everything lower case.
     * @param {*} str
     */
    makeFriendlySlug(str) {
        // \W represents any nonalphanumeric character so that, for example, 'A&P Grocery' becomes 'a-p-grocery'
        let friendlySlug = str.replace(/\W+/g, '-').toLowerCase();
        // If the last char was nonalphanumeric, we could end with a hyphen, so trim that off, if so...
        if (friendlySlug.substring(friendlySlug.length - 1) === "-") {
            friendlySlug = friendlySlug.substring(0, friendlySlug.length - 1);
        }
        return friendlySlug;
    }

    handleSubmit = () => {
        // TO DO: validate form

        let blogPost = {...this.state.post};
        // save blogPost here...
    }

    render() {

        const {post: {$id, title, slug, excerpt, body}} = this.state,
            {classes} = this.props

        return (
            <div className="Editor">
                <h1>New Post</h1>
                <p>URL: {$id}</p>
                <form>
                    <TextField
                        label="Title"
                        value={title}
                        onChange={this.handleChange('title')}
                        margin="normal"
                        className={classes.FormControl}
                    />
                    <br/>
                    <TextField
                        label="Slug"
                        value={slug}
                        onChange={this.handleChange('slug')}
                        margin="normal"
                        className={classes.FormControl}
                    />
                    <br/>
                    <TextField
                        label="Excerpt"
                        multiline
                        rowsMax="5"
                        value={excerpt}
                        onChange={this.handleChange('excerpt')}
                        margin="normal"
                        className={classes.FormControl}
                    />
                    <br/>
                    <TextField
                        label="Body"
                        multiline
                        rowsMax="10"
                        value={body}
                        onChange={this.handleChange('body')}
                        margin="normal"
                        className={classes.FormControl}
                    />
                    <br/>
                    <Button
                        variant="contained"
                        color="primary"
                        className={classes.button}
                        onClick={this.handleSubmit}>Submit</Button>
                </form>
            </div>
        );
    }
}

export default withStyles(styles)(Editor);

It’s a lot of code, I know. For now, all you need to know is that this creates a basic form for creating new blog posts.

Now, let’s add a route and menu item for this new view.

Add a route for the New Post form view

In src/App.js, import the Editor component with:

import Editor from './components/editor';

Then add the following route before the closing </Switch> tag:

<Route path='/blog/posts/new' component={Editor}/>

You can verify this route works by manually entering the address in your browser URL address bar (http://localhost:3000/blog/posts/new).

Add a menu item for the New Post form view

We’ll create a New Post link in the menu now. In src/layout/index.js, add the following import for the menu item icon:

import CreateIcon from '@material-ui/icons/Create';

Now update const drawer in the same file. Add the following after the closing </List> and just before the closing </div> defined within const drawer.

<Divider/>
<ListItem component={NavLink} to="/blog/posts/new" button>
    <ListItemIcon>
        <CreateIcon />
    </ListItemIcon>
    <ListItemText primary="New Post" />
</ListItem>

Now, we have a new menu item that links to our editor component.

New Post form view

Commit: Add New Post menu item and view

Wrap up

In this tutorial series we are demonstrating how you can build a simple blog engine with Carbon LDP and React. Today, we focused only on the front-end and completed the following tasks. We:

    – Reviewed the end goal (requirements and features)
    – Created the project skeleton using Create React App
    – Installed Material UI
    – Implemented responsive drawer (a responsive fly-out menu)
    – Implemented navigation routes
    – Linked the menu items
    – Created the New Post form view, it’s route, and menu item

We now have a front-end that is very basic, but functionally interactive. In Part 2, we’ll bring in the Carbon LDP JavaScript SDK so that we can use Carbon as a back-end for persisting application data.