Building a Gallery using React.js, Webpack, Bootstrap, and SASS (CSS)

For the past few months, I’ve been using React for a few projects, and I’d like to show you how to hit the ground running with the Facebook’s library.
Moreover combined with Webpack (and a bunch of other tools), React becomes a joy to work with. Hence, in the first part of this tutorial we will build the environment setup to get you going with React, Webpack, npm, and Babel.
After that we will have a react starter kit to use as a starting point for working on our application. It will be a fully functionnal and reusable Image Gallery.
Here’s a preview :

Image Gallery Preview

Slideshow Preview
After reading this tutorial, you should be able to understand fundamentals concepts in React, and easily take what you’ve learned to create your own front-end applications.
You can grab the complete project on Github.
Hello, React!
First things first, let’s begin by building a minimal React application displaying the “Hello, React!” message on browser.
So, create a folder for your project and the HTML file for the entry point of our application. Create an index.html file with the following content:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello, React!</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react-dom.js"></script>
</head>
<body>
<div class="container"></div>
</body>
<script type="text/javascript">
var App = React.createClass({
render: function () {
return React.createElement('h1', null, 'Hello, React!');
}
});
var options = {};
var element = React.createElement(App, options);
ReactDOM.render(element, document.querySelector('.container'));
</script>
</html>
Now if you open the browser, you can see the “Hello, React!” message.
In order to get the most out of the features of JSX, bundling, and ES6 with React we need tools like Webpack and Babel. So, in the following parts, we are going to see how to setup a React development environment with these technologies.
Setting up the project
You can find the complete code for the react starter kit on Github.
Let’s start from scratch and hit npm init in your terminal. Fill in some answers (you can accept the default for all the prompts). You should have a package.json file that looks like the following:
{
"name": "react-starter",
"version": "1.0.0",
"description": "A React Starter Kit to use as a starting point for working on simple ReactJS app",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/gdangelo/react-starter.git"
},
"keywords": [
"ReactJS"
],
"author": "Grégory D'Angelo",
"license": "ISC",
"bugs": {
"url": "https://github.com/gdangelo/react-starter/issues"
},
"homepage": "https://github.com/gdangelo/react-starter#readme"
}1. Installing and configuring Webpack
Webpack is the perfect module bundler for using it along with React. It takes our application code (with dependencies) and generates static assets based on some configuration.
It also has a development server that serve these static files and watch for changes in our source files. The bundling process is executed on change.
So, install it using npm as a dev dependency:
npm install --save-dev webpack
Next we need to create the webpack.config.js file that carry all the configuration settings needed to Webpack to perform its work. Add the following basic settings in your config file :
var path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, './build'),
filename: "bundle.js"
}
};The entry specifies the entry file of our React application. It will be use by Webpack as the starting point for the bundling process.
The output tells Webpack what to do after completing the bundling process. In our case, the build/ directory will be use to output the bundled file named bundle.js.
Now that we have the basic settings in place, we’ll need to move our React codebase from index.html to index.js. We will also require react and react-dom libraries from index.js.
First modify the index.html by removing the react libraries and the JS code. Then, add a script tag at the bottom for our bundle.js file. index.html should look like this :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello, React!</title>
</head>
<body>
<div class="container"></div>
<script type="text/javascript" src="build/bundle.js"></script>
</body>
</html>
Install react and react.dom using npm :
npm install --save react react-dom
Then, create the index.js file in ./src:
var React = require('react');
var ReactDOM = require('react-dom');
var App = require('./components/App/App');
var element = React.createElement(App, {});
ReactDOM.render(element, document.querySelector('.container'));Notice that we’ve required and rendered a React component named App.
Let’s create the App.js required under ./components/App:
var React = require('react');
module.exports = React.createClass({
render: function () {
return React.createElement('h1', null, 'Hello, React!');
}
});Now run the following command in your terminal :
./node_modules/.bin/webpack
It generates the bundle.js in the ./build directory. You can open the index.html in your browser and see the same “Hello, React!” message as before.
2. Setting up Babel-loader
To use JSX syntax and ES6 in our React application we’ll need to use a tool which translates them to vanilla JavaScript.
It will be done with Webpack and loaders. Loaders are used by Webpack to process files before bundling them.
We’ll use Babel to preprocess files. We can setup the Babel loader by installing the following npm packages:
npm install --save-dev babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0
The babel-preset-es2015 and babel-preset-react are Babel plugins used to compile ES2015 to ES5 and transform JSX syntax into plain JS respectively.
In order to perform the corresponding translations we need to tell Babel to use these two plugins by adding the following line to the .babelrc file:
{
"presets" : ["es2015", "react", "stage-0"]
}Notice that we’ve also enabled ES7 features by installing and loading the babel-preset-stage-0 package.
Now we need to update the Webpack configuration settings to use the babel-loader:
var path = require("path");
module.exports = {
entry: "./src/index",
output: {
path: path.resolve(__dirname, './build'),
filename: "bundle.js"
},
resolve: {
extensions: ['', '.js', '.jsx']
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loaders: ['babel']
}
]
}
};In the configuration file we’ve defined an array of loaders that only contains the babel-loader for now. It applies to files that match the test regex and exclude files that match the exclude regex.
In our case it will process all files with a .js or .jsx extension except the ones inside the node_modules folder.
We can now use JSX and ES6 in our React application:
// App.jsx
import React, { Component } from 'react';
class App extends Component {
render() {
return <h1>Hello, React!</h1>;
}
}
export default App;
// index.js
import React from 'react';
import { render } from 'react-dom';
import App from './components/App/App';
let element = React.createElement(App, {});
render(element, document.querySelector('.container'));
Once again run Webpack. It should bundle successfully.
3. Configuring Webpack Dev Server and React Hot loader
As mentionned before, Webpack comes with a development server that reruns the bundling process while updating our code.
Hence, we’ll use WebpackDevServer to compile and serve our code in development. Install it via npm:
npm install --save-dev webpack-dev-server
Next update the webpack.config.js file as follow:
var path = require("path");
var webpack = require("webpack");
module.exports = {
entry: [
'webpack-dev-server/client?http://localhost:8080', // WebpackDevServer host and port
'./src/index' // Your appʼs entry point
],
output: {
path: path.resolve(__dirname, 'build'),
publicPath: '/build/',
filename: "bundle.js"
},
resolve: {
extensions: ['', '.js', '.jsx']
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
],
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loaders: ['babel']
}
]
}
};Note that we added a client host and port to the webpack-dev-server entry point.
It is also important to specify the output.publicPath that matches the output.path from the view of the index.html page.
Finally, we added a section for plugins and required HotModuleReplacementPlugin and NoErrorsPlugin.
As the names suggest, the first one enables Hot Module Replacement and the other one skips the emitting phase when encountering errors.
Now, you can run the WebpackDevServer with the following command and open the http://localhost:8080 URL in browser:
./node_modules/.bin/webpack-dev-server
Make some changes in the React App component, and save your file. Notice the bundling process on your terminal and the changes happen live in browser.
However in order to have live refresh while editing our React components by preserving the state we’ll use the React Hot Loader plugin.
The only npm package that we need to install is react-hot-loader:
npm install --save-dev react-hot-loader
Once more, update the webpack.config.js file:
var path = require("path");
var webpack = require("webpack");
module.exports = {
entry: [
'webpack-dev-server/client?http://localhost:8080', // WebpackDevServer host and port
'webpack/hot/only-dev-server', // "only" prevents reload on syntax errors
'./src/index' // Your appʼs entry point
],
output: {
path: path.resolve(__dirname, './build'),
publicPath: '/build/',
filename: "bundle.js"
},
resolve: {
extensions: ['', '.js', '.jsx']
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
],
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loaders: ['react-hot', 'babel']
}
]
}
};We added the hot reloading server before our app’s entry point, and put the React Hot Loader before babel in the loaders array.
Finally, add the following command to package.json in order to use npm as a task runner:
"scripts": {
"start": "webpack-dev-server"
}Run npm start. Live refresh should now work with React Hot Loader.
4. Adding styles with Sass
To add some power and elegance to our stylesheets we’ll make use of [Sass][9].
As for JSX, we will use loaders to process Sass-like markup in our CSS.
Install them with npm:
npm install --save-dev style-loader css-loader postcss-loader postcss autoprefixer precss
style-loader and css-loader allow us to load css file in our React components. Whereas, postcss-loader is a loader to process our CSS with plugins. In our case we’ll only use autoprefixer and precss.
Let’s add them in our webpack.config.js file:
var path = require("path");
var webpack = require("webpack");
var autoprefixer = require('autoprefixer');
var precss = require('precss');
module.exports = {
entry: [
'webpack-dev-server/client?http://localhost:8080', // WebpackDevServer host and port
'./src/index' // Your appʼs entry point
],
output: {
path: path.resolve(__dirname, 'build'),
publicPath: '/build/',
filename: "bundle.js"
},
resolve: {
extensions: ['', '.js', '.jsx']
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
],
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loaders: ['babel']
}, {
test: /\.scss$/,
loaders: ['style-loader', 'css-loader', 'postcss-loader']
}
]
},
postcss: function () {
return [autoprefixer, precss];
}
};In our project, each component will have its own scss file. Moreover a common scss file will hold all variables used accross every components.
Create the variables.scssfile in ./src/components and add some variables:
/*
* Colors
* ========================================================================== */
$blue: #e9f0f5;
$grey: #ccc;
$grey-dark: #89949B;
$grey-light: #B8C3C8;
$red: #EA7882;
/*
* Typography
* ========================================================================== */
$font-family-base: 'Segoe UI', 'HelveticaNeue-Light', sans-serif;
/*
* Layout
* ========================================================================== */
$max-content-width: 1000px;
Then add the App.scss file in ./src/components/App with the following content:
@import '../../../node_modules/normalize.css/normalize.css';
@import '../variables.scss';
/*
* Base styles
* ========================================================================== */
html {
color: #222;
font-weight: 100;
font-size: 1em; /* ~16px; */
font-family: $font-family-base;
line-height: 1.375; /* ~22px */
}
.container {
margin: 0 auto;
padding: 0 0 40px;
max-width: $max-content-width;
}
Notice that we used normalize.css. So, you need to install it via npm:
npm install --save normalize.css
Finally, require the stylesheet inside our React component:
import React, { Component } from 'react';
import s from './App.scss';
class App extends Component {
render() {
return <h1>Hello, React!</h1>;
}
}
export default App;Your project should now be structured like so:
- src
- components
- App
- App.jsx
- App.scss
- index.js
- variables.scss
- .babelrc
- index.html
- package.json
That’s the end of the environment setup. Let’s build the Image Gallery without further delay !
Building the Image Gallery
Before we do anything, let’s include Bootstrap to our app via Bower. Hit bower init in your terminal and answer questions.
Then, install Bootstrap:
bower install --save bootstrap
Next, update the following lines to your index.html file to include bootstrap:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Image Gallery</title>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container"></div>
<script src="bower_components/jquery/dist/jquery.min.js"></script>
<script src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
<script type="text/javascript" src="build/bundle.js"></script>
</body>
</html>
Now, we’ll implement the gallery item as a separate React component. So, create ./src/components/GalleryItem.jsx and fill it with the following content:
import React, { Component } from 'react';
import s from './GalleryItem.scss';
class GalleryItem extends Component {
constructor(props) {
super(props);
// Manually bind this method to the component instance...
this.handleIncrement = this.handleIncrement.bind(this);
this.handleMouseEnter = this.handleMouseEnter.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
}
static defaultProps = {
title: "Camera",
subtitle: "Lorem ipsum",
image: "https://cloud.githubusercontent.com/assets/4352286/11920432/f0aaff34-a76f-11e5-8456-a5d78b089233.jpg"
}
state = {
hovering: false,
liked: this.props.liked || false,
counts: (Math.round(Math.random() * 20) + 4)
}
handleIncrement() {
if(this.state.liked) return;
this.setState({
liked: true,
counts: this.state.counts+1
});
}
render() {
return (
<div className="col-xs-6 col-sm-4">
<div className="thumbnail">
<div
className="image-preview"
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}>
<a className="gallery" href={this.props.image} title={this.props.title}>
{this.state.hovering ? this.icons() : null}
<img src={this.props.image} alt={this.props.title} />
</a>
</div>
<div className="caption text-center">
<h4>{this.props.title}</h4>
<h5>{this.props.subtitle}</h5>
<a className={"btn btn-primary" + (this.state.liked ? " liked" : "")} onClick={this.handleIncrement}>
<span className="glyphicon glyphicon-heart"></span> {this.state.counts}
</a>
</div>
</div>
</div>
);
}
icons() {
return <div className="overlay"><span className="glyphicon glyphicon-zoom-in"></span></div>;
}
handleMouseEnter() {
this.setState({hovering: true});
}
handleMouseLeave () {
this.setState({hovering: false});
}
};
export default GalleryItem;In the static defaultProps method, we’re setting all the default properties needed to render a thumbnail element. Any clicks on the like button will trigger an update of the state through handleIncrement method. Futhermore, onMouseEnter and onMouseLeave events have been added to the image in order to show a zoom-in icon.
To style the component, create the following ./src/components/GalleryItem.scss file:
@import '../variables.scss';
/*
* GalleryItem styles
* ========================================================================== */
.btn {
border: 2px solid $grey;
border-radius: 20px;
padding: 3px 20px;
}
.btn-primary {
background-color: white;
color: $grey-dark;
&:hover, &:focus, &:active, &.liked {
background-color: $red;
border-color: $red;
color: white;
}
}
.thumbnail {
border-radius: 8px;
padding: 12.5px;
.image-preview {
display: inline-block;
position: relative;
margin: 0 auto;
img {
width: 100%;
height: 100%;
display: inline-block;
}
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: black;
opacity: 0.7;
cursor: pointer;
}
.glyphicon-zoom-in {
font-size: 40px;
color: white;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
}
.caption {
padding: 0;
text-transform: uppercase;
h5 {
color: $grey-light;
}
}
}
As we defined our GalleryItem as a reusable component, we can write much less code to build the entire image gallery.
Let’s add some items to a wrapper component by creating the following file, ./src/components/Gallery/Gallery.jsx:
import React, { Component } from 'react';
import GalleryItem from '../GalleryItem/GalleryItem';
import s from './Gallery.scss';
class Gallery extends Component {
constructor(props) {
super(props);
// Manually bind this method to the component instance...
this.renderItems = this.renderItems.bind(this);
}
render() {
return (
<div className="row">
{this.renderItems()}
</div>
);
}
renderItems() {
return this.props.items.map(function(item){
return <GalleryItem key={item.id} {...item} />;
});
}
};
export default Gallery;Then pass properties from index.html to Gallery.jsx through App.jsx:
// index.js
import React from 'react';
import { render } from 'react-dom';
import App from './components/App/App';
let options = {
galleryItems: [
{ id: 1, title: "Camera", subtitle: "5th Jan - 12th Jan", image: "https://cloud.githubusercontent.com/assets/4352286/11920432/f0aaff34-a76f-11e5-8456-a5d78b089233.jpg", liked: true },
{ id: 2, title: "Notebook", subtitle: "13th Feb - 5th Aug", image: "https://cloud.githubusercontent.com/assets/4352286/11920434/f0ac4d44-a76f-11e5-9190-b93bc72688ed.jpg" },
{ id: 3, title: "Thinking", subtitle: "13th Dec - 14th Dec", image: "https://cloud.githubusercontent.com/assets/4352286/11920433/f0ab6a28-a76f-11e5-8ee9-525dc22ca8a8.jpg" }
]
};
let element = React.createElement(App, options);
render(element, document.querySelector('.container'));
// App.jsx
import React, { Component } from 'react';
import Gallery from '../Gallery/Gallery';
import s from './App.scss';
class App extends Component {
render() {
return <Gallery items={this.props.galleryItems} />;
}
}
export default App;
So we have a nice image gallery here, but it isn’t all that great if we can’t zoom in on images and see slideshow. In order to have a slideshow we’ll include a third party library to simplify the code of our Gallery.
Install the blueimp-gallery package with bower:
bower install --save blueimp-gallery
Load it in index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Image Gallery</title>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="bower_components/blueimp-gallery/css/blueimp-gallery.min.css">
</head>
<body>
<div class="container"></div>
<div id='blueimp-gallery' class='blueimp-gallery blueimp-gallery-controls'>
<div class='slides'></div>
<h3 class='title'></h3>
<p class="description"></p>
<a class='prev'>‹</a>
<a class='next'>›</a>
<a class='close'>×</a>
<a class='play-pause'></a>
<ol class='indicator'></ol>
</div>
<script src="bower_components/jquery/dist/jquery.min.js"></script>
<script src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="bower_components/blueimp-gallery/js/blueimp-gallery.min.js"></script>
<script type="text/javascript" src="build/bundle.js"></script>
</body>
</html>
Finally, update Gallery.jsx to use blueimp to show the slideshow on click:
import React, { Component } from 'react';
import GalleryItem from '../GalleryItem/GalleryItem';
import s from './Gallery.scss';
class Gallery extends Component {
constructor(props) {
super(props);
// Manually bind this method to the component instance...
this.renderItems = this.renderItems.bind(this);
}
componentDidMount() {
let links = document.getElementsByClassName('gallery');
$('.gallery').unbind('click').bind('click', event => {
blueimp.Gallery(links, {
index: event.currentTarget,
event: event
});
});
}
render() {
return (
<div className="row">
{this.renderItems()}
</div>
);
}
renderItems() {
return this.props.items.map(function(item){
return <GalleryItem key={item.id} {...item} />;
});
}
};
export default Gallery;As usual, run npm start, open your browser and see the result.
Conclusion
In today’s tutorial, we setup a React.js environment on which we built the Image Gallery. We showcased how Webpack and Babel can be used to gain productivity while developping React.js application.
If you have been following along with the source or building from scratch, you should now be able to build your own UI based on our React.js development environment. Else, fork the Github repo and have fun!

Subscribe Now for Exclusive Content
Originally published at EloquentWebApp.