Category Archives: TUTORIALS

Detailed tutorials explaining how to build most common applications, in different languages.

Setting up production server.

branch-name:  
Click To Copy

 

Developing config is adding a lot of heavy processing in order to have source mapping working and debugging easy, but we don’t need this in production. All we need is speed. Let’s create another config that we are going to use for production build.

There are different ways to achieve this. One way for example is to create a common config and one for production and one for development. Then use webpack-merge plugin to combine the configs like it was explained here , or we could simply create another Webpack config for running the production server, but then we have to manage multiple configs which is far from perfect, so let’s use the same concept like before and extend (and override) the base config with production one.

Creating production config.

Add

./webpack.prod.config.js

const webpack = require('webpack');
let config = require('./webpack.base.config.js');

config.mode = "production";
config.devtool = "";

config.entry = [
    './src/index.js', 
];

module.exports = config;

also lets modify the base config to use plain JavaScript syntax since Webpack won’t know how to run it and at this point there is no Babel teanspiler to convert the code to plain JavaScript.

./webpack.base.config.js

const getEnvironmentConstants = require('./getEnvironmentConstants');
const webpack =require('webpack');

module.exports = {
  mode: 'development',
  devtool: 'eval-source-map',
  entry: [
    '@babel/polyfill',    
    './src/index.js',
  ],
  output: {
    filename: '[name]-bundle.js',
    publicPath: '/dist/',
  },  
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },

      // SCSS
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 2,
              localIdentName: '[folder]-[local]',
              sourceMap: true
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: () => [require('autoprefixer')()],
              sourceMap: true
            },
          },
          {
            loader: 'sass-loader',
            options: {
              outputStyle: 'expanded',
              sourceMap: true
            }
          }
        ],
      },
      // images
      {
        test: /\.(png|jp(e*)g|svg)$/,  
        use: [{
            loader: 'url-loader',
            options: { 
                limit: 8000, // Convert images < 8kb to base64 strings
                name: 'images/[hash]-[name].[ext]'
            } 
        }]
      },
      //File loader used to load fonts
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: ['file-loader']
      }                    
    ]
  },
  plugins: [
    new webpack.DefinePlugin({ 'process.env' : getEnvironmentConstants() } )
  ]
};

Creating production build.

Edit package.json and add clean script to remove the dist folder and build-prod script.

./package.json

...
"scripts": {
  "start-cli": "webpack-dev-server --hot --history-api-fallback --config webpack.cli.config.js",
  "start-api": "babel-node server-api.js",
  "start-middleware": "babel-node server-middleware.js",
  "clean": "rm -rf ./dist",
  "lint": "eslint .",
  "build-dev": "webpack --mode development",
  "build-prod": "webpack --config webpack.prod.config.js",
},
...

what we just did:
– we added a cleanup script clean that will clean up the production build directory (line 6)
– we added a production build script (line 9)

Now we could clear the ./dest folder by running yarn clean then do the production build yarn build-prod and have production bundle ready.

Creating Express production server.

There are different ways to serve your files once the production build is done. One way is to use nginx server which I might create a tutorial of how to set up later, or we could use the express server.

Let’s create an Express config and run a simple prod server. Create the prod.server.js with the following contents:

./prod.server.js

const express = require('express');
const path = require('path')
const app = express();
const port = 8080;

app.use('/dist', express.static('dist'));
app.get('*', (req, res)=>{
  res.sendFile(path.join(__dirname, 'index.html'));
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`))

 what we just did:
– we created an Express server
– we told the server to serve every static file like JS, CSS, images from the /dist folder (line 6)
– everything else that is not matching the static content falls into ‘*’ and will serve index.html

Looks familiar? It’s pretty similar to that we did in the previous chapters.

Let’s add a production server start script:

./package.json

"scripts": {
  "start-cli": "webpack-dev-server --hot --history-api-fallback --config webpack.cli.config.js",
  "start-api": "babel-node server-api.js",
  "start-middleware": "babel-node server-middleware.js",
  "clean": "rm -rf ./dist",
  "lint": "eslint .",
  "build-dev": "webpack --mode development",
  "build-prod": "webpack --config webpack.prod.config.js",
  "run-prod-server": "node ./prod.server.js",
  "build-and-run-prod-server": "yarn clean && yarn build-prod && yarn run-prod-server"
},

 what we just did:
– we created `run-prod-server` that will simply start the express server with will statically serve the contents of the /dist folder where build-prod script is dumping the bundle.
– To make things easier we also created a new script entry (line 10) that does all necessary steps to run production server:
– calls yarn clean script to remove the ./dist folder before rebuilding
– calls yarn build-prod to make a fresh production buid
– tells node to start using ./prod.server.js that we just created as an entry point, which starts the Express server.

Give it a try yarn run-prod-server and observe the ./dist folder. It will disappear for a brief moment, then it will re-appear with the fresh content and once you see this message in the console ‘example app listening on port 8080!’ you are ready to hit the browser url.

If everything went well, you should see the site running, using the production Express server.

Extracting CSS in a separate files.

Right now, if we look at the ./dist folder we will see only *.js and the font file that we added, which means that all CSS is embedded into Javascript files, which is ok for development, but far from ideal for production. Now the browser can’t cache the CSS, and we will see issues with styling the components since it will take time for the CSS to be applied. To solve this problem we have to extract the CSS from JS and load it separately.

Adding mini-css-extract-plugin.

And there is a plug-in for this called mini-css-extract-plugin. Let’s install it.

yarn add mini-css-extract-plugin

and let’s tell Webpack to use it only with the production build:

./webpack.prod.config.js

const webpack = require('webpack');
let config = require('./webpack.base.config.js');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

config.mode = "production";
config.devtool = "";

config.entry = [
    './src/index.js', 
];

config.module.rules[1].use[0] = MiniCssExtractPlugin.loader;

config.plugins = [ ... [new MiniCssExtractPlugin({
        // these are optional
        filename: "[name].css",
        chunkFilename: "[id].css"
    })], 
    ... config.plugins
];

module.exports = config;

 what we just did:
– we replaced ‘style-loader’ with mini-css-extract-plugin to extract all CSS files form JS bundle (line 12)
– by default even without any parameters mini-css-extract-plugin will create two bundle types: one with the main.css and the rest per component base. Which means that each component will have their own CSS bundle, dynamically loaded when the component is rendered. For a large project this will dramatically reduce the initial CSS load.

You could play with the optional parameters: filename and chunkFilename and see how this will affect generated CSS files names. If for example you would like to rename the main file to global-main.css you could set filename: "global-[name].css" or if you want to change the chunk names you could do this: chunkFilename: "[id].css" . Parameters in the brackets are automatically replaced  by Miini-css-extract-plugin. [id] – will be replaced with the chunk id # for example.

Rebuild the project:

yarn build-prod

And let’s look at ./dist folder. Below every JS bundle we should be able to see the corresponding CSS for this module. Now it looks better. Run the production server build-and-run-prod-server load the project and observe the net tab while navigating in different site sections. You will see the css files loading for each component once you navigate to a new section. But we are missing the main.css which is not loading on demans. Let’s add a tag to load it:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Babel Webpack Boilerplate</title>
        <link rel="stylesheet" type="text/css" href="dist/main.css">
    </head>
    <body>
        <div id="root"></div>
        <script type="text/javascript" src="dist/main-bundle.js"></script>
    </body>
</html>

Run the production server again and the app should look like before.

But there is another problem: click to see the file content’s and you will notice that the CSS is not minified.

Minimizing the CSS.

Let’s use optimize-css-assets-webpack-plugin

yarn add optimize-css-assets-webpack-plugin --dev

./webpack.prod.config.js

const webpack = require('webpack');
let config = require('./webpack.base.config.js');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");

config.mode = "production";
config.devtool = "";

config.entry = [
    './src/index.js', 
];

config.module.rules[1].use[0] = MiniCssExtractPlugin.loader;

config.plugins = [ ... [new MiniCssExtractPlugin({
        // these are optional
        filename: "[name].css",
        chunkFilename: "[id].css"
    })],
    new OptimizeCSSAssetsPlugin({}),   
    ... config.plugins
];

module.exports = config;

Rebuild production and run the prod server and if you open some of the CSS file contents you will see that they are now minified.

Just what we need for production build!

branch-name:  
Click To Copy

 

 

Adding environment variables file.

branch-name:  
Click To Copy

 

Although Wepack already comes with two modes: development and production, It comes handy to have different environment variables for development and production and to store them in different files.

Let’s create .env file which will be our development environment variable file.

Creating the .env file

./.env

APP_NAME=Webpack React Tutorial
GRAPHQL_URL=http://localhost:4001/graphql

As it looks like .env is not a standart JavaScriupt object so we can’t use the variables out of the box. We will need two modules Dotenv and Dotenv-expand.

Adding and configuring Dotenv to load variables into process.env.

Dotenv is module that loads environment variables from a .env file into process.env.

yarn add dotenv dotenv-expand

Let’s give it a try and print out GRAPHQL_URL in the backend. Let’s do this using server-api.js config. All that we need to do is to ‘tell’ Dotenv module to load variables into process.env

./src/server-api.js

import WebpackDevServer from 'webpack-dev-server';
import webpack from 'webpack';
import config from './webpack.api.config.js';
require('dotenv').config();

console.log(">>>" + process.env.GRAPHQL_URL);

const compiler = webpack(config);
const server = new WebpackDevServer(compiler, {
  hot: true,
  publicPath: config.output.publicPath,
  historyApiFallback: true
});
server.listen(8080, 'localhost', function() {});

Run the project and in the backend console you will see GraphQL url printed out.

Now, let’s create getEnvironmentConstants helper method that will use Dotenv to load variables, and in addition we will add a filter that will load only these variables to the front end that we specify in frontendConstants . This way important variables that we need in the backend like passwords to the database won’t be exposed in the source code.

./getEnvironmentConstants.js

const fs = require('fs');

// Load environment variables from these files
const dotenvFiles = [
  '.env'
];


// expose environment variables to the frontend
const frontendConstants = [
  'APP_NAME',
  'GRAPHQL_URL'
];

function getEnvironmentConstants() {
  
  dotenvFiles.forEach(dotenvFile => {
    if (fs.existsSync(dotenvFile)) {
      require('dotenv-expand')(
        require('dotenv').config({
          path: dotenvFile,
        })
      );
    }
  });
  
  const arrayToObject = (array) =>
  array.reduce((obj, item, key) => {
    obj[item] = JSON.stringify(process.env[item]);
    return obj
  }, {})

  return arrayToObject(frontendConstants);      
}

module.exports = getEnvironmentConstants;

Once we have the object in place we could use the DefinePlugin to pass them to the frontend.

Adding the DefinePlugin.

if we go back in this tutorial we will remember that we showed how to configure Webpack in three different ways: using CLI, the webpack API and the server middleware.
Now the best place to add DefinePlugin will be in webpack.base.config so all three Webpack set-ups will take advantage of it. Let’s import getEnvironmentConstants and pass it as a parameter to DefinePlugin.

./webpack.base.config.js

import getEnvironmentConstants from './getEnvironmentConstants';
import webpack from 'webpack';

module.exports = {
  mode: 'development',
  devtool: 'eval-source-map',
  entry: [
    '@babel/polyfill',    
    './src/index.js',
  ],
  output: {
    filename: '[name]-bundle.js',
    publicPath: '/dist',
  },  
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },

      // SCSS
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 2,
              localIdentName: '[folder]-[local]',
              sourceMap: true
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: () => [require('autoprefixer')()],
              sourceMap: true
            },
          },
          {
            loader: 'sass-loader',
            options: {
              outputStyle: 'expanded',
              sourceMap: true
            }
          }
        ],
      },
      // images
      {
        test: /\.(png|jp(e*)g|svg)$/,  
        use: [{
            loader: 'url-loader',
            options: { 
                limit: 8000, // Convert images < 8kb to base64 strings
                name: 'images/[hash]-[name].[ext]'
            } 
        }]
      },
      //File loader used to load fonts
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: ['file-loader']
      }                    
    ]
  },
  plugins: [
    new webpack.DefinePlugin({ 'process.env' : getEnvironmentConstants() } )
  ]
};

 what we just did:
– (line 1 and 2) we imported getEnvironmentConstants and Webpack since we will need it to instantiate the plug in.
– (line 72-74) we added the plug in.

We have to do one more change in order to have the plug-in working for all Webpack configs:

./webpack.api.config.js

const webpack = require('webpack');
let config = require('./webpack.base.config.js');

config.entry = [
  '@babel/polyfill',    
  './src/index.js',
  'webpack/hot/dev-server',
  'webpack-dev-server/client?http://localhost:8080/',       
];

config.plugins = [... [new webpack.HotModuleReplacementPlugin()], ... config.plugins ];

module.exports = config;

and

./webpack.middleware.config

const webpack = require('webpack');
let config = require('./webpack.base.config.js');

config.entry = [
  '@babel/polyfill',    
  './src/index.js',
  'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000',        
];

config.plugins = [... [new webpack.HotModuleReplacementPlugin()], ... config.plugins ];

module.exports = config;

 what we just did:
Since we added config.plugins array in webpack.base.config we don’t want to override it here and lose the changes. That’s why we are merging the array using spread operator.
If you are unfamiliar with the spread operator you might read the link above. Basically what it does is to ‘spread’ two or more arrays into the current array.

var a = [1,2,3];

var b = [...a, ...[4,5,6]];

console.log(b);

result: (6) [1, 2, 3, 4, 5, 6]

Accessing env variables in the front-end.

And let’s load these variables. We could replace the hardcoded GraphQL url with the one from the .env file.

And using the variables on the back end is straight forward: we just include .env file and use the variables, but passing them to the front end requires a little bit more effort. We have to use the DefinePlugin which will allow us to create global constants which can be configured at compile time.

./src/components/App/index.js

import React, { Component } from 'react';
import PageLayout from '../../containers/PageLayout';
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { Provider } from 'react-redux';
import { createStore} from 'redux';
import reducers from '../../reducers';

import styles from './styles.scss';

const store = createStore(reducers, {});
export default class App extends Component {
  render() {
    const GRAPHQL_URL = process.env.GRAPHQL_URL;
    const client = new ApolloClient({
      link: new HttpLink({ uri:  GRAPHQL_URL }),
      cache: new InMemoryCache()
    });  
    return (
      <div className={styles.appWrapper}>
        <Provider store={store}>
          <ApolloProvider client={client}>
            <Router>
              <Switch>
              <Route exact path="*" component={PageLayout} />  
              </Switch>
            </Router>
          </ApolloProvider>
        </Provider>
      </div>        
    );
  }
}

 

And we could also print the APP name in the header section process.env.APP_NAME

./src/components/Header/index.js

import React from 'react';
import { Link } from 'react-router-dom';
const styles = require('./styles.scss');
const Header = ( {title} ) => (
  <div>
    <div className={styles.wrapper}>      
      <h2>{ title } { process.env.APP_NAME } </h2>
      <ul>
        <li><Link to='/home'>HOME</Link></li>
        <li><Link to='/greetings'>GREETINGS</Link></li>       
        <li><Link to='/dogs-catalog'>DOGS CATALOG</Link></li>
        <li><Link to='/about'>ABOUT</Link></li>
      </ul>
    </div>
  </div>
);
export default Header;

Now, start the server using yarn start-api and if everything works fine you will see the “Webpack React Tutorial” in the header.

 

branch-name:  
Click To Copy

 

 

Breaking the schema into separate group of files.

Having the entire schema into the server.js file is not practical, especially if the schema grows bigger, so let’s move the schema into separate folders for easy management:

mkdir ./src/schema/queries -p
mkdir ./src/schema/types -p
mkdir ./src/schema/mutations -p

./src/schema/index.js

const graphql = require('graphql');
const queries = require('./queries');
const mutations = require('./mutations');

var rootQuery = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {...queries, ...mutations },
});

module.exports = new graphql.GraphQLSchema({
  query: rootQuery
});

./src/schema/types/index.js

let Dogs = require('./dogs.js');

module.exports = {
  ...Dogs,
};

./src/schema/types/dogs.js

const graphql = require('graphql');

module.exports = new graphql.GraphQLObjectType({
  name: 'dogType',
  fields: {
    id: { type: graphql.GraphQLString },
    breed: { type: graphql.GraphQLString },
    displayImage: { type: graphql.GraphQLString },
  }
});

./src/schema/queries/index.js

let Dogs = require('./dogs.js');

module.exports = {
  ...Dogs
};

./src/schema/queries/dogs.js

const graphql = require('graphql');
const dogType = require('../types/dogs');
const dogs = require('../../models/mock_data/dogs.js');

module.exports = {
  getDogByBreed: {
    type: dogType,
    args: {
      breed: { type: graphql.GraphQLString }
    },
    resolve: function (_, {breed}) {
      var result = dogs.find(function(dog){
        return breed == dog.breed;
      });
      return result;
    }
  } 
}

./src/schema/mutations/index.js

let Dogs = require('./dogs.js');

module.exports = {
  ...Dogs
};

./src/schema/mutations/dogs.js

const graphql = require('graphql');
const dogs = require('../../models/mock_data/dogs.js');

module.exports = {
  addDogBreed: {
    type: graphql.GraphQLString,
    args: {
      id: { type: graphql.GraphQLString },
      breed: { type: graphql.GraphQLString },
      displayImage: { type: graphql.GraphQLString }
    },
    resolve: function (_, {id, breed, displayImage}) {
      dogs.push({
        id: id,
        breed: breed,
        displayImage: displayImage
      });
      return "OK!";
    }
  }   
}

./server.js

var express = require('express');
var graphqlHTTP = require('express-graphql');
const schema = require('./src/schema');


// Logger middleware
var logger = function(req, res, next) {
  console.log("GOT REQUEST >", req.ip);
  next(); // Passing the request to the next handler in the stack.
}

var app = express();
app.use(logger);
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

 

Setting up faster development environment using Nodemon and Babel.

Adding Nodemon

Nodemon is the same like node with the benefit that it will monitor the developing folder and restart automatically,, which makes the developing process more convenient. Let’s go ahead and install it:

yarn add nodemon --dev

And replace the node with nodemon in the start script:

./package.json

{
  "name": "graphql-tutorial",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "nodemon server.js"
  },
  "dependencies": {
    "express": "^4.16.4",
    "express-graphql": "^0.7.1",
    "graphql": "^14.0.2"
  },
  "devDependencies": {
    "nodemon": "^1.18.9"
  }
}

Now, whenever we edit and save a file Nodemon will conveniently restart for us and reflect the new changes.

If we also want to get advantage of the latest ES syntax we would like to install Babel.

 

Adding Babel 7

Starting with Babel7 the config setting changes quite a bit.

  1. Babels packages are now scoped and Babel has renamed it’s NPM packages. This means babel-cli for example has been renamed to @babel/cli .
  2. No messing around with presets anymore. You can just use @babel/preset-env now and optionally define your requirements in the config file.
  3. babel-node has been moved from the CLI to it’s own package: @babel/node

Let’s go ahead and install all necessary packages:

yarn add @babel/core @babel/cli @babel/node --dev

and tell nodemon to use Babel to transpile JS.

./package.json

{
  "name": "graphql-tutorial",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "nodemon --inspect=10111 --exec babel-node server.js"
  },
  "dependencies": {
    "express": "^4.16.4",
    "express-graphql": "^0.7.1",
    "graphql": "^14.0.2"
  },
  "devDependencies": {
    "nodemon": "^1.18.9"
  }
}

 

Before we could start using the new ES features we have to install a preset and tell Babel to use it by adding ./babelrc config file

yarn add @babel/preset-env --dev

add

./.babelrc

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "entry"
      }
    ]
  ]
}

 

give it a try …

./server.js

import express from 'express';
import graphqlHTTP from 'express-graphql';
import schema from './src/schema';


// Logger middleware
var logger = function(req, res, next) {
  console.log("GOT REQUEST >", req.ip);
  next(); // Passing the request to the next handler in the stack.
}

var app = express();
app.use(logger);
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

 

Using GraphQLSchema to construct the schema programmatically.

For most of the cases defining a fixed schema when the application starts, by adding Query and Mutation types solely using schema language is good enough. But sometimes we might need to define a dynamic schema and we can achieve this by creating a new JS objects.

Construct dynamic schema for ‘User Greeting’ example.

Let’s get again to the ‘Greetings user’ example, because of it’s simplicity and define a Query with field named greetingUser which will accept userName and bornMonth parameters, first of type string and the second of type int and return userType.

And the userType will return greetingOne  which will simply say “Hello [userName]” and greetingTwo  that will let the user know how many months left till their next birthday. Both of type string.

./src/server.js

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
var dogs = require('./src/models/mock_data/dogs.js');
const graphql = require('graphql');

// Define the User type
var userType = new graphql.GraphQLObjectType({
  name: 'UserType',
  fields: {
    greetingOne: { type: graphql.GraphQLString },
    greetingTwo: { type: graphql.GraphQLString },
  }
});

// Define the Query type
var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    greetingUser: {
      type: userType,
      args: {
        userName: { type: graphql.GraphQLString },
        bornMonth: { type: graphql.GraphQLInt }
      },
      resolve: function (_, {userName, bornMonth}) {
        var date = new Date();
        var daysLeft = bornMonth - (date.getMonth() + 1);
        daysLeft = daysLeft < 0 ? daysLeft + 12 : daysLeft;
        return {
          greetingOne: `Hello ${userName}`,
          greetingTwo: `Your birthday is comming in ${daysLeft} month(s)`
        };
      }
    }
  }
});

var schema = new graphql.GraphQLSchema({query: queryType});


// Logger middleware
var logger = function(req, res, next) {
  console.log("GOT REQUEST >", req.ip);
  next(); // Passing the request to the next handler in the stack.
}

var app = express();
app.use(logger);
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

what we just did:
– we defined the user type which is pretty self explanatory (Lines 8-14)
– then we created the query type, that has these parameters:
type which is the return type, in this case userType
args is the input parameter types.
resolve is the resolver function.

Transform Dogs catalog to use dynamic schema.

Creating the dog type

var dogType = new graphql.GraphQLObjectType({
  name: 'dogType',
  fields: {
    id: { type: graphql.GraphQLString },
    breed: { type: graphql.GraphQLString },
    displayImage: { type: graphql.GraphQLString },
  }
});

Creating the query type

// Define the Query type
var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    getDogByBreed: {
      type: dogType,
      args: {
        breed: { type: graphql.GraphQLString }
      },
      resolve: function (_, {breed}) {

        var result = dogs.find(function(dog){
          return breed == dog.breed;
        });
        return result;
      }
    }
  }
});

Creating a mutation type

...
    addDogBreed: {
      type: graphql.GraphQLString,
      args: {
        id: { type: graphql.GraphQLString },
        breed: { type: graphql.GraphQLString },
        displayImage: { type: graphql.GraphQLString }
      },
      resolve: function (_, {id, breed, displayImage}) {
        dogs.push({
          id: id,
          breed: breed,
          displayImage: displayImage
        });
        return "OK!";
      }
    } 
...

Adding the query schema

...
var schema = new graphql.GraphQLSchema({query: queryType});

...
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
...

Putting it all together

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
var dogs = require('./src/models/mock_data/dogs.js');
const graphql = require('graphql');

// Define the dogs type
var dogType = new graphql.GraphQLObjectType({
  name: 'dogType',
  fields: {
    id: { type: graphql.GraphQLString },
    breed: { type: graphql.GraphQLString },
    displayImage: { type: graphql.GraphQLString },
  }
});

// Define the Query type
var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    getDogByBreed: {
      type: dogType,
      args: {
        breed: { type: graphql.GraphQLString }
      },
      resolve: function (_, {breed}) {

        var result = dogs.find(function(dog){
          return breed == dog.breed;
        });
        return result;
      }
    },
    addDogBreed: {
      type: graphql.GraphQLString,
      args: {
        id: { type: graphql.GraphQLString },
        breed: { type: graphql.GraphQLString },
        displayImage: { type: graphql.GraphQLString }
      },
      resolve: function (_, {id, breed, displayImage}) {
        dogs.push({
          id: id,
          breed: breed,
          displayImage: displayImage
        });
        return "OK!";
      }
    }    
  }
});


var schema = new graphql.GraphQLSchema({query: queryType});


// Logger middleware
var logger = function(req, res, next) {
  console.log("GOT REQUEST >", req.ip);
  next(); // Passing the request to the next handler in the stack.
}

var app = express();
app.use(logger);
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

GraphQL schemas, queries and resolvers.

In the previous example we learned how to run GraphQL with Express http server and we created a simple query example with returns hardcoded data. Let’s make this useful and create real world example.

Adding Nodemon for rapid development.

Before we continue, let’s make development process easier and run the project using Nodemon instead of Node. Nodemon is the same like Node but it also monitors the project folder for changes, and automatically restarts the server for us.

yarn add nodemon --dev

Creating schema and resolvers.

Technically speaking we already created our schema and resolver in the previous example. Let’s quickly go through what we did:

The most basic way to create a response in GraphQL is like it was described in the previous tutorial: creating schema describing the field name and type i.e.

var schema = buildSchema(`
  type Query {
    hello: String
  }
`);

and then creating a resolver i.e.

var root = {
  hello: () => {
    return 'Hello world!';
  },
};

which simply returns whatever is in the return statement: in this case “hello world!” for any hello field request.

Let’s make the example more useful by

Passing parameters.

The basic types that we could use out of the box are StringIntFloatBoolean, and ID

Let’s change the hello query to accept userName parameter of type String and prints ‘hello [userName]’. Also we will add another field called rollDice and will return a random number between 0 and 10.
Passing parameters requires two steps:
– define the parameter in the schema (line 8) hello(userName:String!): String
– read the parameter in the resolver (line 15) hello: (args) => { ...

./server.js

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');

// Construct a schema, using GraphQL schema language
var schema = buildSchema(`
  type Query {
    hello(userName:String!): String,
    rollDice: Int
  }
`);
// The root provides a resolver function for each API endpoint

var root = {
  hello: (args) => {
    return `Hello ${args.userName}`;
  },
  rollDice: () => {
    return Math.floor(Math.random() * 6) + 1;
  }
};

var app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));

app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

 what we just did:
– we added userName parameter in the schema and defined it as s String type (line 8)
– in the resolver we are getting the userName parameter from the args parameter, and returning the greeting line 16.
– in addition we also added another field call rollDice which returns a random number between 0 and 10.

Making queries with parameters.

Save the file and navigate the browser to http://localhost:4000/graphql

GraphQL UI

 

Explore the schema using documentation explorer.

Look at the top right side in the browser (picture above) and you will find the Documentation explorer (If it is collapsed click on ‘Docs’ in the upper right section). Let’s pretend that we didn’t build the schema but we still need to know what queries we could fire. That’s where Documentation explorer could help.
Click on Query and you will see this:

hello(userNameString!): String
This describes the ‘hello’ and the ‘rollDice’ queries, the input parameters and the output type.

Making the query.

Since now we know the query name and the parameters and their types we are ready to write our first query with parameters.

Add the query in the up left text box like it’s shown on the picture above,

query queryLabel($userName: String!) {
  hello(userName: $userName)
}

and then add the parameter in the left, down box named query variables. When we write query with parameters it’s always better instead to directly pass the parameter value, to use the $ syntax to define a variable in the query.

{
  "userName": "Sam Smith"
}

 what we just did:
– we described a query with label queryLabel and input parameter called $userName of type String
– The ! symbol means that $userName can’t be null
– (line 2) we are querying hello and passing $userName as a parameter
– in the second code snipped (under query variables) we are passing the actual value of our parameters. In our case the value of $userName

Hit the play button in the upper left section and you will get the response:

{
  "data": {
    "hello": "Hello Sam Smith"
  }
}

If we also want to query the rollDice we could just add this to our query:

query queryLabel($userName: String!) {
  hello(userName: $userName)
  rollDice
}

The great thing about GraphQL is that it will return only the queries that we requested, and also we could combine multiple queries (from the same schema) in one response.

Adding Redux

branch-name:  
Click To Copy

 

In the previous tutorial we showed how to build custom Redux like store, which is a great way to understand how Redux work, but if we want a production grade app, we have to do more work on our custom Redux like store, since we cheated a bit.

For example our redux store emits setState no matter if the properties are actually changed or not which is ok for a demo app but for a big app with a lot of mapStateToProps it will have performance impact, so let’s not re-invent the wheel but use the Redux store instead.

yarn add redux react-redux

Since Redux like implementation that we did in the previous tutorial is very similar to the actual Redux, there are just a few changes that we need to do in order to start using Redux.

./src/components/App/index.js

import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import PageLayout from '../../containers/PageLayout';
import { Provider } from 'react-redux';
import { createStore} from 'redux';
import reducers from '../../reducers';

import fetch from 'unfetch';


const store = createStore(reducers, {});

export default class App extends Component {

  render() {

    const GRAPHQL_URL = 'http://localhost:4001/graphql';
    const client = new ApolloClient({
      link: new HttpLink({ uri:  GRAPHQL_URL, fetch: fetch }),
      cache: new InMemoryCache()
    });  

    return (
      <Provider store={store}>
        <ApolloProvider client={client}>
          <Router>
            <Switch>
              <Route exact path="*" component={PageLayout} />    
            </Switch>
          </Router>
        </ApolloProvider>
      </Provider>
    );
  }
}

 what we just did:
– we removed our custom store implementation:

import Store from ‘../../store’;
import Reducers from ‘../../reducers’;
import Provider from ‘../../containers/Provider’;


– and replaced it with the Redux provider and create store (line 8 and 9)
– we called the Redux createStore (line 15)
– finally we wrapped the app with the new provider passing the store (line 28)

Next step is just cleaning up the code a bit.
– We renamed the index.js reducer to ./src/reducers/useer.js so we could keep each different reducer types in separate files and we added one main reducer ./src/reducers/index.js that will combine all other reducers. (in our case ./src/reducers/useer.js for now)

./src/reducers/user.js

const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';

const initialState = {
  todos: [],
  userName: "No Name",
  editMode: false
};

  const reducer = (state = initialState, action) => {
    switch (action.type) {
      case CHANGE_USERNAME: {
        let newState = {...state};
        newState.userName = action.data;
        return newState;
      }

      case TOGGLE_EDIT_MODE: {
        let newState = {...state};
        newState.editMode = !newState.editMode;
        return newState; 
      }

      default:
        return state;
    }
  };

export default reducer;

 

./src/reducers/index.js

import { combineReducers } from 'redux';
import user from './user';

export default combineReducers({
  user
});

 

And the best part is that since our Redux like implementation was so similar to the actual Redux implementation, we have to make just two little changes:
– Replace import connect from '../../containers/Provider/connect'; with the Redux connect import { connect } from 'react-redux';
– And since we added combineReducers we have to add user property to access the users reducer.

./src/components/About/index.js ./src/components/Greetings/index.js.

import React, { Component } from 'react';
import { connect } from 'react-redux';

const styles = require('./styles.scss');

const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';

class Greetings extends Component {

  constructor(props) {
    super(props); 
  }
  

  doneEditUsername() {
    let newName = document.querySelector('#inputField').value;
    this.props.changeUserName(newName);
    this.props.toggleLogInPopup();
  }

  usernameChanged(el) {
    let newName = el.target.value;    
    this.props.changeUserName(newName);
  }

  onToggleEditMode() {
    this.props.toggleLogInPopup();
  }

  render() {
    let element = <h2 onClick={() =>{   this.onToggleEditMode()  }}>Hello:  {this.props.userName}</h2>;
    if(this.props.editMode)
      element = <h2>Type new name:<input type="text" id='inputField' value={this.props.userName} onChange={(el) => { this.usernameChanged(el);}} /><button onClick={() =>{ this.doneEditUsername() }}>done</button></h2>
    return (
      <div>
        <div className={styles.wrapper}>
          {element}
        </div>
      </div>);
  }
}

const mapStateToProps = ( storeState ) => {
  return {  
    userName: storeState.user.userName,
    editMode: storeState.user.editMode
  }
}

const mapDispatchToProps = dispatch => {
  return {
    toggleLogInPopup: () => {
      dispatch({type: TOGGLE_EDIT_MODE});
    },
    changeUserName: (userName) => {
      dispatch({type: CHANGE_USERNAME, data: userName});
    }
  }
};


export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Greetings);

 

./src/components/About/index.js ./src/components/About/index.js

import React, { Component } from 'react';
import { connect } from 'react-redux';

const CHANGE_USERNAME = 'CHANGE_USERNAME';
class About extends Component {
  constructor(props) {
    super(props);
    this.state = {
      userName: this.props.userName,
    };    
  }
  handleChange() {
    const userName = document.querySelector('input[name=username]').value;
    this.setState( { userName: userName } );
    this.props.onEdit(userName);
  }
  render() {
    return (
      <div>
        <p>This is <input type="text" name="username" value={this.state.userName} onChange={() => { this.handleChange()}} /></p>
      </div>
    );
  }
}
//export default About;
const mapStateToProps = storeState => ({
  userName: storeState.user.userName
}
);
const mapDispatchToProps = dispatch => ({
  onEdit: (userName) => dispatch({
    type: CHANGE_USERNAME,
    data: userName
  })
});
const AboutContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(About);
export default AboutContainer;

 

And delete ./src/containers/Provider folder.

And now all custom Redux like implementation was replaced wit the actual Redux store. Give it a try and everything shoul;d work like before but using the actual Redux store.

 

branch-name:  
Click To Copy

 

Plus One

Task

Given a non-empty array of digits representing a non-negative integer, plus one to the integer.

The digits are stored such that the most significant digit is at the head of the list, and each element in the array contain a single digit.

You may assume the integer does not contain any leading zero, except the number 0 itself.

Example 1:

Input:

 [1,2,3]

Output:

 [1,2,4]

Explanation:

 The array represents the integer 123.

Example 2:

Input:

 [4,3,2,1]

Output:

 [4,3,2,2]

Explanation:

 The array represents the integer 4321.

This problem was taken from Leetcode

Solution

The solution:

The solution is pretty straight forward. We traverse all digits in the opposite direction, and make sure that we add +1 only on the last digit (the first iteration ). Then if the digit > 9 we set up the dit to 0, and we carry on 1 to add it to the next digit, and keep going till we reach the first digit. If the first digit is 9 and cary over is not 0, we add 1 to the beginning of the array.

let’s consider: 9 9 9

iteration 1:
9            9
9            9
9 + 1 =  0 + cary on: 1

iteration 2:
9            9
9 + 1 =  0 + cary on: 1
0            0

iteration 3:
9 + 1 = 0 + cary on: 1
0          0
0          0

finally:
1     << adding leading 1 
0
0
0

which gave up the final result: 1 0 0 0

The solution will look like this:

Java Script

/**
 * @param {number[]} digits
 * @return {number[]}
 */
var plusOne = function(digits) {
    
    var carryOn = 0;
    for(q = digits.length - 1; q!=-1; q--) {
        var digit = digits[q];
        if(q == digits.length - 1) {
            digit = digit + 1;
            if(digit == 10) {
                digit = 0;
                carryOn = 1;
            }
        }
        else {
            if(digit == 9 && carryOn) {
                digit = 0;
            }
            else {
                digit = digit + carryOn;
                carryOn = 0;
            }
        }
        digits[q] = digit;
    }
    if(carryOn > 0) 
        digits.unshift(1);
    return digits;
};

 

what we just did:
– (lines 11 – 18) happened only if this is the last digit, where we have to add 1
– (lines 18 – 20) if we have carry on value of carryOn > 0 and digit = 9 simply set digit = 0 and left carryOn to be equal to 1 for the next iteration.

Build Redux like custom store.

 This exercise is a bit more challenging, that’s why I will add two git branches: one with the middle of the tutorial and one with the final product.

Although we are not going to use our custom Redux like store in the feature tutorials, it will be rewarding to be able to create your own ‘Redux like’ store.

You will understand how Redux work and why there is no magic happening there but a plain JavaScript is doing the job.

branch-name:  
Click To Copy
branch-name:  
Click To Copy

Local component state.

Let’s modify the Greeting component to use class instead of function and see how the state is tied up with the component.

./src/components/Greetings/index.js

import React, { Component } from 'react';
const styles = require('./styles.scss');
const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';
class Greetings extends Component {
  constructor(props) {
    super(props); 
    this.state = {
      userName: "no name",
      editMode: false
      }
  }  
  doneEditUsername() {
    let newName = document.querySelector('#inputField').value;
    this.setState({userName: newName, editMode: false});
  }
  usernameChanged(el) {
    this.setState({ userName: el.target.value });
  }
  render() {
    let element = <h2 onClick={() =>{ this.setState({editMode: true});    }}>Hello:  {this.state.userName}</h2>;
    if(this.state.editMode)
      element = <h2>Type new name:<input type="text" id='inputField' value={this.state.userName} onChange={(el) => { this.usernameChanged(el)}} /><button onClick={() =>{ this.doneEditUsername() }}>done</button></h2>
    return (
  <div>
    <div className={styles.wrapper}>
      {element}
    </div>
  </div>);
  }
}
export default Greetings;

 what we just did:
– we set up the initial state into the constructor with userName set to “no name” and editMode set to false, so the component just simply shows “Hello: no name”
– in the render function we are checking if component is in edit mode editMode parameter and rendering either the <h2> tag with “Hello: [USERNAME] or the tag with editable <input> (line 23)
– we added doneEditUsername() function hooked to the ‘done’ button, which will simply set the state userName to whatever was the user input.
– also added usernameChanged() function  that will set the new username state so the input tag will reflect the new component name. Otherwise the input tag will be read only.

Now if you give it a try and play around switching to a different pages you will notice that the component won’t preserve the changes and it will default to “no name” each time when you switch back and forth to some other page. This happens because the local state is not connected to a store.

Creating persistent store.

In order for our component to preserve the changes, we need to have some sort of persistent store. There are many ways to achieve this but let’s explore the simplest one.

Use plain global object as a store.

This simply could be achieved by adding a store object to the global window object, and set up the default values (lines 13 – 16)

./src/components/App/index.js

import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { ApolloProvider, graphql } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import PageLayout from '../../containers/PageLayout';
import Store from '../../store';
import Reducers from '../../reducers';

import fetch from 'unfetch';

window.store = {
  userName: "no name",
  editMode: false
}

export default class App extends Component {

  render() {

    const GRAPHQL_URL = 'http://localhost:4001/graphql';
    const client = new ApolloClient({
      link: new HttpLink({ uri:  GRAPHQL_URL, fetch: fetch }),
      cache: new InMemoryCache()
    });  

    return (
      <ApolloProvider client={client}>
        <Router>
          <Switch>
            <Route exact path="*" component={PageLayout} />    
          </Switch>
        </Router>
      </ApolloProvider>
    );
  }
}

and let’s hook our new store:

./src/components/Greetings/index.js

import React, { Component } from 'react';
const styles = require('./styles.scss');
const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';
class Greetings extends Component {
  constructor(props) {
    super(props); 
    this.state = {
      userName: window.store.userName,
      editMode: window.store.editMode
      }
  }  
  doneEditUsername() {
    let newName = document.querySelector('#inputField').value;
    window.store.userName = newName;
    window.store.editMode = false;
    this.setState({userName: window.store.userName , editMode: window.store.editMode});
  }

  usernameChanged(el) {
    window.store.userName = el.target.value;
    this.setState({ userName: window.store.userName });
  }

  render() {
    let element = <h2 onClick={() =>{ window.store.editMode = !window.store.editMode; this.setState({editMode: window.store.editMode});    }}>Hello:  {this.state.userName}</h2>;
    if(this.state.editMode)
      element = <h2>Type new name:<input type="text" id='inputField' value={this.state.userName} onChange={(el) => { this.usernameChanged(el)}} /><button onClick={() =>{ this.doneEditUsername() }}>done</button></h2>
    return (
  <div>
    <div className={styles.wrapper}>
      {element}
    </div>
  </div>);
  }

}
export default Greetings;

what we just did:
– we hooked window.store to feed the local state properties. (Lines 9 and 10). This way when the component is re-rendered next time it will get these values from the persistent global store.
– we changed doneEditUsername() and usernameChanged() to store the values of the userName and editMode into our global store and used these values to set the state and update the component.
– we updated the value of window.store.editMode to be equal to the opposite value of itself so we could toggle the edit mode  window.store.editMode=!window.store.editMode (line 26)

Now our component preservers the state. Even if you toggle the edit mode, navigate to some different section and get back, the component will conveniently wait into the editing mode, with the input field ready to get the new user name.

 Cool right now we have functional persistent state, but the way it was implemented is a mess!

Why ?
– We are setting the store parameters from many different places in the code, which soon will become a nightmare to debug.
– we are mutating the state, one more reason why debugging will become almost impossible.

Let’s fix this.

Doing it the Redux way.

Let’s create a CreateStore function (line 10 below) that will expose three helper methods:

  • dispatch(action) – which with take action parameter which will look like this for example: {type: ‘CHANGE_USER_NAME’, data: ‘New User Name’ }
  • getState() – will return current state. This way we will protect the state from direct mutations. Only dispatching an actions could do this.
  • subscribe(handler) – will be fired on component will mount and will pass a handler function that will react on state change, and call setState to update the local state and trigger UI update. The handler function for the Greetings component will look like this:
    ...
          // this function is called when dispatch method is called and state is changed.
          const newState = window.store.getState();
          this.setState( {userName: newState.userName,
                          editMode: newState.editMode
          });
    ...

    this function will also return the unsubscribe function that we have to fire on componentWillUnmaunt

Let’s start with creating the store:

./src/store/index.js

const validateAction = action => {
  if (!action || typeof action !== 'object' || Array.isArray(action)) {
    throw new Error('Action must be an object!');
  }
  if (typeof action.type === 'undefined') {
    throw new Error('Action must have a type!');
  }
};

const createStore = reducer => {
  let state;
  const subscribers = [];

  const store = {
    
    dispatch: action => {
      validateAction(action);
      state = reducer(state, action);
      subscribers.forEach(handler => handler());
    },

    getState: () => state,

    subscribe: handler => {
      subscribers.push(handler);
      return () => {
        const index = subscribers.indexOf(handler);
        if (index !== -1) {
          subscribers.splice(index, 1);
        }
      };
    }
  };

  // Returns the initial state
  store.dispatch({type: '--INIT--'});
  return store;
};
export default createStore;

 what we just did:
– like we described above, we created createStore function that will return an object with dispatch, getState and subscribe functions (lines 16,22,24);
dispatch will take an action and will pass it to the reducer, which will match the appropriate action type and will update the store (line 18).
Next it will call all subscribed functions, which will react on state update and will do setState to trigger UI update.
getState simply returns the new state and protects the state object from accidental mutation.
– subscribe function accepts handler function which will be invoked when the dispatch method was called to dispatch an action. It also returns another function to unsubscribe from the store and remove the handler from the subscribers array (line 26)

And now let’s do the reducer which simply will take the initial state, and then we will use the classic switch-case to match the action.type and return the new state. So right now only the reducers are able to update the state, making debugging much more predictable.

./src/reducers/index.js

const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';

const initialState = {
  userName: "No Name",
  editMode: false
};

  const reducer = (state = initialState, action) => {
    switch (action.type) {

      case CHANGE_USERNAME: {
        let newState = {...state};
        newState.userName = action.data;
        newState.editMode = false;
        return newState;
      }

      case TOGGLE_EDIT_MODE: {
        let newState = {...state};
        newState.editMode = !newState.editMode;
        return newState; 
      }

      default:
        return state;
    }
  };

export default reducer;

Let’s instantiate the store and attach it to the window object so it will be accessible by the components:

./src/components/App/index.js

import React, { Component } from 'react';
import PageLayout from '../../containers/PageLayout';
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Store from '../../store';
import Reducers from '../../reducers';
import styles from './styles.scss';


window.store = Store(Reducers);

export default class App extends Component {
  render() {
    const GRAPHQL_URL = 'http://localhost:4001/graphql';
    const client = new ApolloClient({
      link: new HttpLink({ uri:  GRAPHQL_URL }),
      cache: new InMemoryCache()
    });  
    return (
      <div className={styles.appWrapper}>
        <ApolloProvider client={client}>
          <Router>
            <Switch>
            <Route exact path="*" component={PageLayout} />  
            </Switch>
          </Router>
        </ApolloProvider>
      </div>        
    );
  }
}

and now let’s use our new store in the Greeting component:

./src/components/Greetings/index.js

import React, { Component } from 'react';
const styles = require('./styles.scss');

const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';
class Greetings extends Component {
  
  constructor(props) {
    super(props); 
    const store = window.store.getState();
    let userName = store.userName;
    let editMode = store.editMode;

    this.state = {
      userName: userName,
      editMode: editMode
      }
  }  

  componentWillMount() {
    this.unsubscribe = window.store.subscribe(() => {
      // this function is called when dispatch method is called and state is changed.
      const newState = window.store.getState();
      this.setState( {userName: newState.userName,
                      editMode: newState.editMode
      });
    });  
  }
  componentWillUnmount() {
    this.unsubscribe();
  }  

  doneEditUsername() {
    let newName = document.querySelector('#inputField').value;
    window.store.dispatch({type: CHANGE_USERNAME, data: newName });  
  }

  usernameChanged(el) {
    this.setState({ userName: el.target.value });
  }

  render() {
    let element = <h2 onClick={() =>{ window.store.dispatch({type: TOGGLE_EDIT_MODE });     }}>Hello:  {this.state.userName}</h2>;
    if(this.state.editMode)
      element = <h2>Type new name:<input type="text" id='inputField' value={this.state.userName} onChange={(el) => { this.usernameChanged(el);}} /><button onClick={() =>{ this.doneEditUsername() }}>done</button></h2>
    return (
  <div>
    <div className={styles.wrapper}>
      {element}
    </div>
  </div>);
  }

}
export default Greetings;

Well done! Now it’s less messy, but the implementation still requires a lot of work to wire up the components, also we expose the store as a global variable which is a bit messy.

branch-name:  
Click To Copy

Create Provider and connect to pass properties using React Context.

We could fix this by using React’s Context.
Let’s explain this. Usually, we pass parameters from high order component to the components down below, but if we have to pass parameter from the highest component to the 4th component, we have to keep passing the property as props to all components down to the 4th component. This is quite uncomfortable and tedious, but here is where Context comes handy. Context provides a way to pass data through the component tree without having to pass props down manually at every level.

Let’s do this! We will create:

  • a high order component that will pass the store properties down using the Context. We will call it Provider.
  • a Connect component, which will act like a wrapper component (or factory component) and will transform back the properties from the context and pass them to the wrapped component.

Creating provider component.

./src/containers/Provider/index.js

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

class Provider extends React.Component {
  getChildContext() {
    return {
      store: this.props.store
    };
  }
  render() {
    return this.props.children;
  }
}

Provider.childContextTypes = {
  store: PropTypes.object
};



export default Provider;

 what we just did
– we created a high order component that will convert a store properties into a context property.
– then the component will render the child components.

It’s pretty straight forward. Now we have the store properties passed into the context, and we will need a way to retrieve them back. For this purpose we will have to create a connect component.

Creating connect factory component.

./src/containers/Provider/connect.js

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

const connect = (
    mapStateToProps = () => ({}),
    mapDispatchToProps = () => ({})
  ) => Component => {
    class Connected extends React.Component {

      onStoreOrPropsChange(props) {
        const {store} = this.context;
        const state = store.getState();

        const stateProps = mapStateToProps(state, props);
        const dispatchProps = mapDispatchToProps(store.dispatch, props);
        this.setState({
          ...stateProps,
          ...dispatchProps
        });
      }

      componentWillMount() {
        const {store} = this.context;
        this.onStoreOrPropsChange(this.props);
        this.unsubscribe = store.subscribe(() =>
          this.onStoreOrPropsChange(this.props)
        );
      }

      componentWillReceiveProps(nextProps) {
        this.onStoreOrPropsChange(nextProps);
      }

      componentWillUnmount() {
        this.unsubscribe();
      }
      render() {
        return <Component {...this.props} {...this.state}/>;
      }
    }
  
    Connected.contextTypes = {
      store: PropTypes.object
    };
  
    return Connected;
  };

  export default connect;

 what we just did:
– we created a higher order component factory.
– It takes two functions and returns a function that takes a component and returns a new component, passing the store as prop value.
– the component retrieves back the store from the context (lines 11,12)
– then it calls mapStateToProps and mapDispatchToProps functions which lives in the actual component that we want to connect to the store. These functions does exactly when their names suggest. They map the properties from the store returning the state.
– next the setState is called with the updated state, and the view got updated.

Using the provider component.

This is as simply as wrapping the application with our Provider component.

./src/components/App/index.js

import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { ApolloProvider, graphql } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import PageLayout from '../../containers/PageLayout';
import Store from '../../store';
import Reducers from '../../reducers';
import Provider from '../../containers/Provider';

import fetch from 'unfetch';


let store = Store(Reducers);

export default class App extends Component {

  render() {

    const GRAPHQL_URL = 'http://localhost:4001/graphql';
    const client = new ApolloClient({
      link: new HttpLink({ uri:  GRAPHQL_URL, fetch: fetch }),
      cache: new InMemoryCache()
    });  

    return (
      <Provider store={store}>
        <ApolloProvider client={client}>
          <Router>
            <Switch>
              <Route exact path="*" component={PageLayout} />    
            </Switch>
          </Router>
        </ApolloProvider>
      </Provider>
    );
  }
}

Now the store properties are converted to Context, and passed down.

Use Connect wrapper component.

Now in order to retrieve the store properties and use them, we wrap the components that should get these properties from the store with the newly created connect component, and clean up the code that we don’t need any more.

./src/components/Greetings/index.js

import React, { Component } from 'react';
import connect from '../../containers/Provider/connect';

const styles = require('./styles.scss');

const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';

class Greetings extends Component {

  constructor(props) {
    super(props); 
  }
  

  doneEditUsername() {
    let newName = document.querySelector('#inputField').value;
    this.props.changeUserName(newName);
    this.props.toggleLogInPopup();
  }

  usernameChanged(el) {
    let newName = el.target.value;    
    this.props.changeUserName(newName);
  }

  onToggleEditMode() {
    this.props.toggleLogInPopup();
  }

  render() {
    let element = <h2 onClick={() =>{   this.onToggleEditMode()  }}>Hello:  {this.props.userName}</h2>;
    if(this.props.editMode)
      element = <h2>Type new name:<input type="text" id='inputField' value={this.props.userName} onChange={(el) => { this.usernameChanged(el);}} /><button onClick={() =>{ this.doneEditUsername() }}>done</button></h2>
    return (
      <div>
        <div className={styles.wrapper}>
          {element}
        </div>
      </div>);
  }
}

const mapStateToProps = ( state ) => {
  return {  
    userName: state.userName,
    editMode: state.editMode
  }
}

const mapDispatchToProps = dispatch => {
  return {
    toggleLogInPopup: () => {
      dispatch({type: TOGGLE_EDIT_MODE});
    },
    changeUserName: (userName) => {
      dispatch({type: CHANGE_USERNAME, data: userName});
    }
  }
};


export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Greetings);

 what we just did:
– we wrapped Greetings component with the ‘connect’ component (lines 63-66) which now passes store properties to the Greetings component.
– we mapped the properties that we are interested in in mapStateToProps so only these properties will be passed to the Greeting component (line 44).
– we mapped the functions that will dispatch actions in mapDispatchToProps (line 51)
– we are calling changeUserName and toggleLogInPopup to dispatch the appropriate actions and update the state.
– we set up the input field to use this.props.userName from the store (line 32) and also set up the ‘Hello’ display to use this.props.userName

Now everything is wired into nice automatic way, and this is in general how Redux works.

Add another store connected component and share the store.

Let’s also add another store connected component and see how both of them could use/edit the same store property. Let’s modify the About component to show/edit the userName property.

import React, { Component } from 'react';
import connect from '../../containers/Provider/connect.js';

const CHANGE_USERNAME = 'CHANGE_USERNAME';
class About extends Component {
  constructor(props) {
    super(props);
    this.state = {
      userName: this.props.userName,
    };    
  }
  handleChange() {
    const userName = document.querySelector('input[name=username]').value;
    this.setState( { userName: userName } );
    this.props.changeUserName(userName);
  }
  render() {
    return (
      <div>
        <p>This is <input type="text" name="username" value={this.state.userName} onChange={() => { this.handleChange()}} /></p>
      </div>
    );
  }
}
//export default About;
const mapStateToProps = storeState => ({
  userName: storeState.userName
}
);
const mapDispatchToProps = dispatch => {
  return {
    changeUserName: (userName) => {
      dispatch({type: CHANGE_USERNAME, data: userName});
    }
  }
};

const AboutContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(About);
export default AboutContainer;

Pretty straight forward! Now no matter where you are going to edit the username both ‘About’ and ‘Greetings’ components will reflect the change.

branch-name:  
Click To Copy
branch-name:  
Click To Copy

 

 

Bundle splitting using react-loadable

branch-name:  
Click To Copy

 

So far we created one bundle that loads the code for all components, no matter if we are going to render them or not. if for example  we just open one page with only one component we don’t need to load the code for all other components that are not on the page. It makes the load process slower, especially with a big site with many components.

Let’s fix this and split the bundle into chunks that contain the code only for the components that are on the page that the user opened, and load dynamically other components when the user navigates to a different sections in the site.

Install react-loadable

This is as simple as adding ‘react-loadable’ component.

yarn add react-loadable

Implementing react-loadabe

Before modifying PageLayout to use react-loadable, let’s create helper component called Loading that will show a spinning bar while the components are loading.

Create ‘Loading’ helper component

create a loading component folder mkdir ./src/components/Loading with index file with these contents:

./src/components/Loading/index.js

import React from 'react';

const styles = require('./styles.scss');

/**
 * HELPER COMPONENT TO DISPLAY LOADING ... AND HANDLE ERRORS WHEN COMPONENTS LOADS DYNAMICALLY 
 * @param {} props 
 */
const Loading = (props) => {
  if (props.error) {
    return (<div className={styles.wrapper}>
              <h3>Error loading component!</h3> 
              <p><b>{props.error.message}</b></p>
              <p>{props.error.stack}</p>
              <div><button onClick={ props.retry }>Retry</button></div>
            </div>);
  } else {
    return <div>Loading...</div>;
  }
} 

export default Loading;

and the styling file:

./src/components/Loading/styles.scss

.wrapper {
  background:rgb(141, 141, 172);
  padding-bottom: 10px;
  h3 {
    background: red;
    text-align: center;
  }
  div{
    text-align: center;
  }
}

 

and replace <div>Loading…</div> with the new component.

./src/containers/PageLayout/index.js

import React, { Component } from 'react';
import ComponentList from './ComponentList';
import Loading from '../../components/Loading';
import query from './query';
import { graphql } from 'react-apollo';

const styles = require('./styles.scss');

class PageLayout extends Component {

    constructor(props) {
      super(props);      
    } 
  
    render() {
      if(!this.props.data.getPageByUrl) {
        return (<Loading />);
      }  

...

Adding react-loadable.

`react-loadable’ works the same way like ‘import’ but will load required components on demand and won’t pre-load the components until it’s time to render them.

./src/containers/PageLayout/ComponentList/index.js

import Loadable from 'react-loadable';
import Loading from '../../../components/Loading';

/* Components */

const Header = Loadable({
  loader: () => import ('../../../components/Header'),
  loading: Loading
});

const Home = Loadable({
  loader: () => import ('../../../components/Home'),
  loading: Loading
});

const About = Loadable({
  loader: () => import ('../../../components/About'),
  loading: Loading
});

const Greetings = Loadable({
  loader: () => import ('../../../components/Greetings'),
  loading: Loading
});

const DogsCatalog = Loadable({
  loader: () => import ('../../../containers/DogsCatalog'),
  loading: Loading
});

export default {
  Home: Home,
  About: About,
  Greetings: Greetings,
  DogsCatalogWithRedux: Gallery,
  Header: Header
}

if you run the code you will notice that it will fail with this error message

`… Support for the experimental syntax ‘dynamicImport’ isn’t currently enabled (7:17)`

This is happening since Babel doesn’t have the appropriate plug-in to transpile dynamic imports. Let’s fix this by installing ‘plugin-syntax-dynamic-import’.

yarn add @babel/plugin-syntax-dynamic-import --dev

then add the plug-in into .babelrc file (line 6)

{
    "presets": [
      "@babel/preset-env",
      "@babel/preset-react"
    ],
    "plugins": ["@babel/plugin-syntax-dynamic-import"]   
  }

Now open the dev console, switch to the net tab and filter by .js and navigate to http://localhost:8080/home You will notice that there will be three bundles loaded: main-bundle.js, dist0-bundle.js and dist1-bundle.js the numbers could very but don’t worry about this as long as you see 3 bundles loaded.These bundles have the code for each component: Header component and the Home compoent. Navigate to the “about” section and you will see another bundle loading: dist2-bundle.js.

Now you have bundle split and loaded whenever the component is about to render. If you navigate back, you will see that the bundle won’t be reloaded.

Now the bundle splitting is done.

Fixing source mapping.

Let’s put debugger breakpoint into DogsComponent and take a look at the file source:

./src/containers/DogsCatalog/index.js

/* eslint-disable no-debugger */
import React, { Component } from 'react';
import { graphql } from 'react-apollo';
import DogDetails from './DogDetails';
import query from './query';
const styles = require('./styles.scss');
class DogsCatalog extends Component {
  constructor(props) {
    super(props);
    this.state = {
        breed: "labrador"
    }
  }
  handleClick(breedType) {
    debugger;
    this.setState({
        breed: breedType
    });
  }
...

 

Give it a try and check the location and you will see something like this:http://localhost:8080/dist0-bundle.js which is not quite understandable.

Let’s fix this. Open base Webpack file, and replace `devtool:'source-map' with devtool: 'cheap-module-eval-source-map' in order to have source mapping working after bundle splitting.

If we want even better source mapping with preserved column # as well we could use devtool: eval-source-map which will give the most detailed information for debugging but will make the building process slower.

There are a good amount of Dev tool config parameters, some are suitable only for development since they make bundle rebuild faster some for production only.

[table id=2 /]

For detailed information about all options visit WebPack dev tool page.

Make sure that the debugger statement is removed before we continue to the next chapter.

 

branch-name:  
Click To Copy