Category Archives: TUTORIALS

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

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

[tabby title=”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

[tabby title=”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.

[tabbyending]

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

 

Adding Apollo provider and introduction to making queries to GraphQL

branch-name:  
Click To Copy

Running GraphQL server to connect to.

 First let’s have GraphQL server running so we could connect to.
How to do this:

  • Install MongoDB because our version of GraphQL is using it and we will need a real database in the future tutorials. So let’s do it now. If you are on MAC and have Homebrew installed this is as simple as executing brew install mongodb from the command line.
  •  Download an run GraphQL setup as well. This will install Database settings that we will need in MogoDB.

yarn setup

then run the server:

yarn start

Now if you  point your browser to http://localhost:4001/graphql you will be able to see GraphQL GUI and you could execute queries there, but this is not the subject of this tutorial. If you are interested of how to set up GraphQL with Express server you could visit this article.

Creating ‘Smart’ component.

The concept of ‘smart’ and ‘dumb’ components is simple: We separate the data fetching from presentational layer by dividing the components into ‘presentational’ which only deal with how to show the data, and ‘container’ components, which only work is to fetch the data from some source. In our case this will be Apollo provider.  (read more …)

Installing dependencies.

yarn add graphql graphql-tag react-apollo apollo-client apollo-link-http apollo-cache-inmemory

  • graphql – query language for APIs created by Facebook.
  • graphql-tag – utilities for parsing GraphQL queries.
  • react-apolllo – utilities that fetch data from GraphQL server and use it in building complex and reactive UIs using the React framework.
  • apollo-client – GraphQL client with integrations for React, Angular, and more.
  • apollo-link-http – interface for modifying control flow of GraphQL requests and fetching GraphQL results
  • apollo-cache-inmemory – Tool for caching GraphQL client with integrations for React, Angular, and more.

yarn add graphql graphql-tag react-apollo apollo-link-http apollo-client apollo-cache-inmemory 

Now if we have all moving peaces in place, we could start building our first ‘connected‘ component, that will fetch data from QgraphQL.

Creating Apollo provider.

Let’s add all necessary components that we will need to connect to GraphQL, and wrap the rotes into Apollo provider .

./src/components/App/index.js

import React, { Component } from 'react';
import Home from '../Home';
import Greetings from '../Greetings';
import About from '../About';
import DogsCatalog from '../../containers/DogsCatalog';
import Header from '../Header';
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 styles from './styles.scss';
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>
            <Header />
            <Switch>
              <Route exact path="/home" user="Test" component={Home} />
              <Route exact path="/greetings" user="Test" component={Greetings} />
              <Route exact path="/dogs-catalog" component={DogsCatalog} />              
              <Route exact path="/about" component={About} />
            </Switch>
          </Router>
        </ApolloProvider>
      </div>        
    );
  }
}

 

what we just did:
– added all necessary components that we will need to connect to GraphQL (lines 7 – 10) .
– Import our new Dogs-Catalog component that we are going to build (line 5).
– created Apollo component, and pass link and cache parameters (lines 15-19).
– wrap the routes into Apollo provider.
– add new route to show out new ‘Dogs Catalog’ component.

Also make sure that you are adding navigation section to the Header component.

import React from 'react';
import { Link } from 'react-router-dom';
const styles = require('./styles.scss');
const Header = ( {title} ) => (
  <div>
    <div className={styles.wrapper}>
      <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;

 

Creating connected component.

We will create a simple component, that will fetch data from GraphQL and will show different dogs breeds depending of which one user will choose.

Create new folder mkdir ./src/containers and add the new index.js for the new ‘Dogs Catalog’  component.

./src/containers/DogsCatalog/index.js

import React from 'react';
import gql from 'graphql-tag';
import { Query } from 'react-apollo';

const GET_DOG = gql`
query 
{
  getDogByBreed(breed: "labrador") {
    id
    breed
    displayImage
  }
}
`

const DogCatalog = () => (
  <Query query={GET_DOG}>
    {({ loading, error, data }) => {
      if (loading) return <div>Loading...</div>;
      if (error) return <div>Error!</div>;

      return (
        <div>
            <span>breed: </span>
            <span>{data.getDogByBreed.breed}</span>
            <br />
            <img src={data.getDogByBreed.displayImage} />
        </div>
      )
    }}
  </Query>
)

export default DogCatalog;

what we just did:
– we created const GET_DOG query and we ‘asked’ GraphQL to getDogByBreed where breed = labrador (for more information of what queries you could execute, you could go to http://localhost:4001/graphql and look at the right side where the schema is defined or read more here)
– we wrapped our new component in Query tag, which passes down three properties: loading, error and data
– we showed the appropriate messages depending of the status of loading and error and on success we rendered the component, showing the returned data

Fire up the server using you prefered method, and  navigate to http://localhost:8080/dogs  and enjoy your new component, showing an image of nice black Labrador!

Refactoring the component.

Move the GraphQL query to a separate file.

Keeping things well organized is very important for maintainability and scaling reasons, so let’s move the query in a separate file.

./src/containers/DogsCatalog/index.js

import React from 'react';
import { graphql } from 'react-apollo';
import query from './query';


const DogCatalog = (props) => {
  if(typeof props.data.getDogByBreed === 'undefined') {
    return (
      <p>Loading ...</p>
    );
  }
  return(
    <div>
        <span>breed: </span>
        <span>{props.data.getDogByBreed.breed}</span>
        <br />
        <img src={props.data.getDogByBreed.displayImage} />
    </div>
  );
}

let breed = 'labrador';

export default graphql(query, {
  options: {
    variables: { 
      breed: breed
    } 
  } 
})(DogCatalog);

Create query.js file with the query.

./src/containers/DogsCatalog/query.js

import gql from 'graphql-tag';

const query = gql`
  query getDogByBreed($breed: String) 
  {
    getDogByBreed(breed: $breed) {
      id
      breed
      displayImage
    }
  }
`
export default query;

 what we just did:
– we moved the query to a separate file query.js
– we include the query (line 3)
– we wrapped the DogsCatalog component into GraphQL component, which will execute the query once the component is mounted, and will pass the result back into props.data .

Adding high order component with dogs breed buttons grid.

What we just created is more like a Dog Details component than Dog catalog, so let’s create a new folder under ./src/containers/DogsCatalog and name it DogDetails, and move  ./src/containers/DogsCatalog/index.js and ./src/containers/DogsCatalog/query.js to the new DogDetails folder.

Then create a new ./src/containers/DogsCatalog/index.js file which will show a gird with buttons, from where users can select which dog breed should be displayed into DogDetails component. Then we will pass the selected breed to the DogDetails component.

./src/containers/DogsCatalog/index.js

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


class DogsCatalog extends Component {

  constructor(props) {
    super(props);
    this.state = {
        breed: "labrador"
    }
  }

  handleClick(breedType) {
    this.setState({
        breed: breedType
    });
  }

  render() {  
    if(typeof this.props.data.getDogsList == 'undefined') {
      return(
        <div>Loading ... </div>
      );
    }
    return(
      <div>
        <p>Dogs catalog</p>
        <div>
          {this.props.data.getDogsList.map( (dog) => {
            return (<button key={dog.id} onClick={ () => { this.handleClick(dog.breed) } }>{dog.breed}</button>);
          })}          
        </div>
        <DogDetails breed={this.state.breed} />
      </div>
    );
  }
}


export default graphql(query, {})(DogsCatalog);

 

./src/containers/DogsCatalog/query.js

import gql from 'graphql-tag';

const query = gql`
query getDogsList
{
  getDogsList {
    id
    breed
  }
}
`
export default query;

 

./src/containers/DogsCatalog/DogDetails/index.js

import React from 'react';
import { graphql } from 'react-apollo';
import query from './query';


const DogDetails = (props) => {
  if(typeof props.data.getDogByBreed === 'undefined') {
    return (
      <p>Loading ...</p>
    );
  }
  return(
    <div>
        <span>breed: </span>
        <span>{props.data.getDogByBreed.breed}</span>
        <br />
        <img src={props.data.getDogByBreed.displayImage} />
    </div>
  );
}

let breed = 'labrador';

export default graphql(query, {
  justADumFunction(ThePropsValuesPassedIntoTheFunction) {
    return {
      variables: {
        breed: ThePropsValuesPassedIntoTheFunction.breed,
      },
    };
  },
})(DogDetails);

 

./src/containers/DogsCatalog/DogDetails/query.js

import gql from 'graphql-tag';

const query = gql`
  query getDogByBreed($breed: String) 
  {
    getDogByBreed(breed: $breed) {
      id
      breed
      displayImage
    }
  }
`
export default query;

We could have download all dog details and pass it to the DogDetails component and save the second query, but for the purpose of exercising working with queries we are not going to do this. Besides imagine that we might need a lot more data for each breed then it’s not wise to pre-load it since this might take significant amount of time.

 

Beautify the component with SASS.

Let’s add some css.

create new file

./src/containers/DogsCatalog/styles.scss

.Wrapper {
  img {
    width: 100%;
  }
  .Buttons {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    grid-gap: 8px;
    max-width: 1200px;
    width: 100%;
    margin: 0 auto;    
  }

  .Buttons > button {
    background: efefef;
    border-radius: 5px;
    transition: 0.5s;
  }

  .Buttons > button:hover {  
    background: silver;
    color:white;
    cursor: pointer;
  }

}

and edit dogs component adding the css styles:

./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) {
    this.setState({
        breed: breedType
    });
  }

  render() {  
    if(typeof this.props.data.getDogsList == 'undefined') {
      return(
        <div>Loading ... </div>
      );
    }
    return(
      <div className={styles.Wrapper}>
        <p>Dogs catalog</p>
        <div className={styles.Buttons}>
          {this.props.data.getDogsList.map( (dog) => {
            return (<button key={dog.id} onClick={ () => { this.handleClick(dog.breed) } }>{dog.breed}</button>);
          })}          
        </div>
        <DogDetails breed={this.state.breed} />
      </div>
    );
  }
}


export default graphql(query, {})(DogsCatalog);

Now our component looks much better.

We could have also move the presentational layer to the components instead, but since this is pretty small amount of code, we are not going to complicate it and will keep the presentational layer here for now.

branch-name:  
Click To Copy

 

 

 

Adding routing

branch-name:  
Click To Copy

Set up Express server to route all requests to the default ./index.html 

  • For CLI instance this could be achieved by just passing –history-api-fallback in package.json file.
    Give it a try: fire up the project using the cli settings yarn start-cli and go to http://127.0.0.1:8080/test for example and you will see the same homepage no matter what is the url.

./package.json

"scripts": {
  "start-cli": "webpack-dev-server --hot --history-api-fallback",
  "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 --mode production"
},
  • For server using API config we are adding historyApiFallback: true (line 12) which basically tells the the dev server to fallback to the default file if it can’t find a route.

./server-api.js

/**
 * Runs a webpack-dev-server, using the API.
 */
import WebpackDevServer from 'webpack-dev-server';
import webpack from 'webpack';
import config from './webpack.api.config.js';

const compiler = webpack(config);
const server = new WebpackDevServer(compiler, {
  hot: true,
  publicPath: config.output.publicPath,
  historyApiFallback: true
});
server.listen(8080, 'localhost', function() {});
  • For the dev-middleware it a bit different approach since we are no longer using the built in express server in Webpack-dev-server since we have Express server already in place. So the solution is to tell Express server to accept all requests ‘*’ and to server the same index.html for all requests (line 18)

./server-middleware.js

const express = require('express');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const webpack = require('webpack');
const webpackConfig = require('./webpack.middleware.config.js');
const app = express();
const path = require('path');
const compiler = webpack(webpackConfig);
app.use(webpackDevMiddleware(compiler, {
  hot: true,
  publicPath: webpackConfig.output.publicPath,
}));
app.use(webpackHotMiddleware(compiler, {
  log: console.log,
  path: '/__webpack_hmr',
  heartbeat: 10 * 1000,
}));
app.get('*', function(req, res) {
  res.sendFile(path.join(__dirname, 'index.html'));
});
const server = app.listen(8080, function() {
  const host = server.address().address;
  const port = server.address().port;
  console.log('App is listening at http://%s:%s', host, port);
});

We did this for all server configurations, but at the end we could just stick with only one, but for now we demonstrate how to set up all three configurations.

Let’s continue with setting up React router.

Setting up React router.

Install React-router-dom

yarn add react-router-dom

Before we continue, let’s add another component so we have two URLs to navigate to.

mkdir ./src/Components/About

and create a simple component that will display a message.

./src/Components/About/index.js

import React from 'react';


const About = () => (
  <div>
    <div>Wellcome to my tutorial!</div>
  </div>
)

export default About;

Now let’s move to adding the react router.

There are different palaces where we could ‘wrap’ our project components with Express router component. We could do it in ./src/index.js but I personally prefer to keep ./src/index.js as simple as possible and would move all complex logic into ./src/App/index.js 

./src/App/index.js

import React, { Component } from 'react';
import Greetings from '../Greetings';
import About from '../About';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

import styles from './styles.scss';
export default class App extends Component {
  render() {
    return (
      <div className={styles.appWrapper}>
        <Router>
          <Switch>
            <Route exact path="/greetings" component={() => <Greetings user="John"/> } />
            <Route exact path="/about" component={About} />
          </Switch>
        </Router>
      </div>        
    );
  }
}

what we just did:
– we added React router and Switch module.
– also we added two routes: ‘/greetings’, and ‘/about’ which will open the corresponding components.
– we wrapped ‘Greetings’ component into an inline function that passes the user parameter, otherwise if you try passing it into the route <Route user="John ... React router will simply ignore it.

Wrapping the component into an inline function to pass parameters will work but is not the best approach. The reason for this is because of performance. According to the official docs…

  “When you use the component props, the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component attribute, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component.”

So what is the better solution? Instead of using component, use the renderprop. render accepts a functional component and that function won’t get unnecessarily remounted like with component. That function will also receive all the same props that component would receive. So you can take those and pass them along to the rendered component.

Let’s fix this:

./src/App/index.js

import React, { Component } from 'react';
import Greetings from '../Greetings';
import About from '../About';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

import styles from './styles.scss';
export default class App extends Component {
  render() {
    return (
      <div className={styles.appWrapper}>
        <Router>
          <Switch>
            <Route exact path="/greetings" render={(props) => <Greetings {...props} user="John" />} />
            <Route exact path="/about" component={About} />
          </Switch>
        </Router>
      </div>        
    );
  }
}

Give it a try and navigate to http://localhost:8080/about  and http://localhost:8080/greetings

Great! Now we have React router set up with  two pages opening two different components.

Adding Navigation

Let’s create a component that will serve as a navigation menu mkdir ./src/components/Header

./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}>
      <ul>
        <li><Link to='/home'>HOME</Link></li>
        <li><Link to='/greetings'>GREETINGS</Link></li>       
        <li><Link to='/about'>ABOUT</Link></li>
      </ul>
    </div>
  </div>
);

export default Header;

and let’s add styles:

./src/components/Header/styles.scss

.wrapper {
  
  ul {
    list-style-type: none;
    margin: 0;
    padding: 0;
    overflow: hidden;
    border: 1px solid #e7e7e7;
    background-color: #f3f3f3;
  }
  li {
    float: left;
  }

  li a {
    display: block;
    color: #666;
    text-align: center;
    padding: 14px 16px;
    text-decoration: none;
  }

  li a:hover:not(.active) {
      background-color: #ddd;
  }

  li a.active {
      color: white;
    
  }
}

 

We used React Link component to allow users to navigate through the site.

Now put the component inside the <Router> component.

./src/components/App/index.js

import React, { Component } from 'react';
import Home from '../Home';
import Greetings from '../Greetings';
import About from '../About';
import Header from '../Header';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

import styles from './styles.scss';
export default class App extends Component {
  render() {
    return (
      <div className={styles.appWrapper}>
        <Router>
          <Header />
          <Switch>
            <Route exact path="/home" component={Home} />            
            <Route exact path="/greetings" render={(props) => <Greetings {...props} user="John" />} />
            <Route exact path="/about" component={About} />
          </Switch>
        </Router>
      </div>        
    );
  }
}

You probably noticed that we have 2 pages but the navigation has 3 links. Let’s add another component just to have more pages to navigate through, besides we will need this component later in this tutorial  mkdir ./src/components/Home

./src/components/Home/index.js

import React from 'react';

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

const Home = () => (
  <div>
    <div className={styles.wrapper}>This is my home section!</div>
  </div>
)

export default Home;

./src/components/Home/styles.scss

.wrapper {
  background: rgb(141, 141, 172);
  color: white;
  text-align: center;
  font-family: MyFont;
}

Start the server and give it a try!

branch-name:  
Click To Copy

 

 

Adding React Library, SASS, Images and font loaders

branch-name:  
Click To Copy

 

React is cool frontend library that updates only the necessary portions of the html. Let’s add it to the project.

Adding React library

yarn add react react-dom

First, let’s keep things more consistent and do some house cleaning. Let’s rename ./src/app.js to ./src/index.js This will be the place where React app will be attached to our HTML and remove ./src/greeting.js

Also change Webpack entering point to reflect app.js filename change:

./webpack.config.js

entry: [
  './src/index.js'
],

Optionally but not necessarily  change the app.js to index.js in the package.json. It’s always a good practice to keep things consistent.

Alternatively you could simply ommit the name of the entrance and Webpack by default will look for src/index.js

Now Let’s do a react app. Remove ./src/app.js and ./src/greeting folder and let’s

Create simple react app

and place it into the components folder that we will create.

mkdir -p src/components/App

and add it’s index file

./src/components/App/index.js

import React, { Component } from 'react';


export default class App extends Component {

  render() {
    return (
      <div>
        <h1>React is running</h1>
      </div>
    );
  }
}

Attach React app to the main html

edit ./src/index.js remove it’s test contents and replace it with this:

./src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';

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

if (module.hot) {
  module.hot.accept();
}

What we just did:
– we imported React and React dom library. React dom is needed to attach React app to the HTML
– we imported newly created React app
– we attached the app to the root container.
– we also added the HMR activation script.

One last thing before we are ready to test is to add the html container where the react app will be attached.

./index.html

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

 

Start the project yarn start

Oh snap! Dev server crashed with error message:
ERROR in ./src/index.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: /Users/toninichev/Examples/WebpackReactReduxApolloTutorial/.babelrc: Error while parsing config – JSON5: invalid end of input at 1:1

Why this is happening? We replaced the plain JavaScript form ./src/index.js with JSX syntax that react is using but Babel doesn’t know how to transpile this. Let’s fix it and add in .babelrc config, the react preset plug-in, and install it.

yarn install @babel/preset-react --dev

./.babelrc

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

Right now we need only @babel/preset-react” and @babel/preset/env plug-ins, but in the feature we will have to use more of the advanced ES6 syntax and add more.

Give it another shot and do yarn start

At this point we should have working react app!

Create React component

Let’s re-create ‘greetings’ component but this time making it React component. Create Greetings folder  mkdir -p ./src/components/Greetings folder and add:

./src/components/Greetings/index.js

import React from 'react';

function Greetings(props) {
  return <div>Hello, {props.user}</div>;
}

export default Greetings;

and render the component.

./src/components/App/index.js

import React, { Component } from 'react';
import Greetings from '../Greetings';

export default class App extends Component {

  render() {
    return (
      <div>
        <h1>React is running</h1>
        <Greetings user="John" />
      </div>
    );
  }
}

What we just did:
– in ./src/greetings/index.js we created a new Greetings component that will take property of the user passed from the higher component and show it. Props stands short for properties.
– in ./src/components/app/index.js we are loading the Greetings component and passing the user name as a property to the Greeting component.

Webpack loaders.

Webpack loaders  allow us to bundle any static resource way beyond JavaScript.

Using SCSS and adding SASS loader.

So far so good. We could see our new React component rendering but no styles.
The simplest way to style our components could be to add css-loader and then to inject the css into the DOM using style-loader.

...
    {
      test: /\.(s)?css$/,
      use: [
        'style-loader',
        'css-loader'
      ]
    },
...

 

But it could be really nice if we could add specific styles only applying to this component.

Go to the terminal, and install these modules:

yarn add style-loader --dev
yarn add css-loader --dev
yarn add postcss-loader --dev
yarn add sass-loader --dev
yarn add node-sass --dev
yarn add autoprefixer --dev

Add the highlighted lines in webpack.config.js

./webpack.config.js

module.exports = {
  mode: 'development',
  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
            }
          }
        ],
      }      
    ]
  }
};

 

 what we just did:
– added sass-loader which will first convert SCSS to plain CSS
– added postcss-loader interprets @import and url() like import/require() and will resolve them.
– added style-loader simply inserts the css into the DOM

Let’s add some styling for our new greetings component:

./src/components/Greetings/styles.scss

.wrapper {
  background: blue;
  h2 {
    color: white;
  }
}

and let’s load it and style the component:

./src/components/Greetings/index.js

import React from 'react';

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

function Greetings(props) {
  return <div className={styles.wrapper}><h2>Hello, {props.user}</h2></div>;
}

export default Greetings;

What we just did:
– we added the new style that will be loaded by JavaScript into the new component (line 3)
– we applied the new style (line 6) by using JSX way of doing this.

If you are not familiar with React and JSX please read the React tutorial first.

Congratulations! Now you have React component that could really be in use!

Adding image loader.

Let’s add url-loader and file-loader and save them as a dev dependencies.

yarn add url-loader file-loader --save

Download a small Home Icon that we are going to add it to our greeting component, and save it into a new folder under ./src folder./src/images/home.png (Make sure that you renamed the file to home.png ). Now let’s add the Webpack config to load images:

./webpack.config.js

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

module.exports = {
  mode: 'development',
  devtool: 'eval-source-map',
  entry: [
    '@babel/polyfill',
    './src/index.js',
    'webpack/hot/dev-server',
    'webpack-dev-server/client?http://localhost:8080/',
  ],
  output: {
    filename: '[name]-bundle.js',
    publicPath: '/dist',
  }, 
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.DefinePlugin({ 'process.env' : getEnvironmentConstants() } )
  ],   
  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: false
            }
          }
        ],
      }, 
      
      // 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]'
            } 
        }]
      }      
      
    ],
  }
};

 what we just did:
– we are testing files for image type (line 64) (png, gpeg or gpg, svg)
– we are using url-loader to load the images.
– url-loader – limit: 8000 means that any image smaller than 8kb will be converted to base64 string and inlined in the bundle. Any other image will be passed to the loader that will pass it as a file.

An now let’s use the image in our Greetings component:

./src/components/Greetings.js

import React from 'react';
const styles = require('./styles.scss');
import homeIcon from '../../images/home.png';

function Greetings(props) {

  return (<div className={styles.wrapper}>
            <img height='75px' width='75px' src={homeIcon} /> 
            <h2>Hello, {props.user}</h2>
          </div>);
}
export default Greetings;

Navigate to the greeting component, and we should see the house image there.

Let’s also check if we can add the image url in the CSS. Remove the image tag from the file above, and add it in the greeting CSS

./src/components/Greetings/styles.scss

.wrapper {
  background-image:url('../../images/home.png');
  height: 500px;
  h2 {
    color: black;
  }
}

navigate to the page again and you will see a big background house in the component.

Adding fonts loader.

Loading fonts is as easy as adding another file-loader as it is shown below.

./webpack.config.js

module.exports = {
  mode: 'development',
  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']
      }                    
    ]
  }
};

then we could download some font for example from fonts.google.com or here and add the font in the css using font face:

./src/components/App/styles.scss

@font-face {
  font-family: 'MyFont';
  src:  url('../../fonts/aclonica/aclonica-regular.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
}

.appWrapper {
  background: rgb(141, 141, 172);
  color: white;
  text-align: center;
  font-family: MyFont;
}

 

Modifying component to use latest ES syntax.

We want to be cool devs and to use the latest technologies so let’s modify our Greeting component to use the latest ES6 syntax and take advantage of Babel that we installed in the previous chapters.

Modify greetings index file as follows:

./src/components/Greetings/index.js

import React from 'react';
import styles from './styles.scss';

function Greetings(props) {
  return(<div className={styles.wrapper}>
          <h2>Hello, {props.user}</h2>
        </div>);
}
export default Greetings;

What we just did:
– (line 2) we refactored the component to use the latest EC6 syntax by replacing require with import
– we removed the image tag since now we are loading the image-background from CSS.

 

branch-name:  
Click To Copy