Author Archives: toni

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 dynamic page layout loaded from GraphQL

branch-name:  
Click To Copy

 

Having page layout hardcoded works fine but it doesn’t let us dynamically modify it without changing the code. What about if we store the page layout in mongoDB, then request it through GraphQL and create component that could dynamically load the page components.

Luckily for us we already have page layout set up in GraphQL which for the homepage looks like this:

module.exports = [
    {
      id: 'home',
      url: '/home',
      layout: [ 
        {
          span: 12,
          components: [
            {
              name: "Header"
            }
          ]
        },
        {
          span: 12,
          components:[
            {
              name: "Home"
            }
          ] 
        }                       
      ]
    }
  ]

It simply shows that the page layout has a header and Home component. We are going to build component that will interpret this and will render a webpage with the requested components. This way we will have the flexibility to:

  • dynamically change page layout by adding and removing components without code change
  • create new pages on the fly without code change.

And if you want to explore all other layouts you could look into GraphQL project under ./src/models/mock_data/pages

Creating PageLayout component to load our modules.

Let’s change App/index.js remove all other routes and redirect everything to the new component that we are going to create (line 21).

./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 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>
            <Switch>
            <Route exact path="*" component={PageLayout} />  
            </Switch>
          </Router>
        </ApolloProvider>
      </div>        
    );
  }
}

Now let’s continue with creating the component itself.

./src/containers/PageLayout/index.js

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

class PageLayout extends Component {

    constructor(props) {
      super(props);      
    } 
  
    render() {
        console.log(this.props.data.getPageByUrl);
        return (
        <div>
            this is my page layout!
        </div>
        );
    }
}

export default graphql(query, {
    options(props) {
      return {
        variables: {
          url: props.history.location.pathname
        },
      };
    },
  })(PageLayout);

./src/containers/PageLayout/query.js

import gql from 'graphql-tag';

const query = gql`
query getPageByUrl($url: String) 
{
  getPageByUrl(url: $url) {
    id
    url
    layout {
      span
      components {
        name
      }        
    }
  }
}
`
export default query;

and when you run the project and look at the browser console you will see the layout structure that we printed out in index.js (line 12)

This basically describes which components and in what order should be rendered for the ‘home’ page.

Hook PageLayot component to render required HTML components.

Create ComponentList sub component with the following contents:

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

import Header from '../../../components/Header';
import Home from '../../../components/Home';
import About from '../../../components/About';
import Greetings from '../../../components/Greetings';
import Gallery from '../../../containers/DogsCatalog';

export default {
  Home: Home,
  About: About,
  Greetings: Greetings
}

This component simply provide a list of all components that we are going to render in our website.

Now modify PageLayout component to add the list and render the HTML components.

./src/containers/PageLayout/index.js

import React, { Component } from 'react';
import ComponentList from './ComponentList';
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(<p>loading ...</p>);
      }     
      
      const allLayout = this.props.data.getPageByUrl.layout.map((layoutList) => {
        const layout = layoutList.components.map((component, id , components) => {
          const componentName = component.name;        
          const ChildComponent = ComponentList[componentName];
          if(typeof ChildComponent === 'undefined') {
            return(
              <div key='{id}' className={styles.error}>Can't find {componentName} component!</div>
            );
          }
          return (
              <ChildComponent key={componentName} />
          );
        });
        return layout;
      });
      return(allLayout);
    }
}

export default graphql(query, {
    options(props) {
      return {
        variables: {
          url: props.history.location.pathname
        },
      };
    },
  })(PageLayout);

 what we just did:
– we added the ComponentList which returns all components on our website
– (Line 19) we are getting the layout for this particular url that contains all components, then we loop through all components (line 20) and create an instances for all of them.
– If the component cannot be found it will show red error message (lines 23-27)

Also add the css which will colorize the error message in red:

./src/containers/PageLayout/styles.scss

.error {
  background: red;
  color: white;
}

navigate to http://localhost:8080/home and you are probably going to see this screen:

This is actually made intentional, to demonstrate how our error handler works If it can’t find particular component.

Let’s fix this. Add the highlighted lines with the missing components.

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

import Header from '../../../components/Header';
import Home from '../../../components/Home';
import About from '../../../components/About';
import Greetings from '../../../components/Greetings';
import Gallery from '../../../containers/DogsCatalog';

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

Navigate again to http://localhost:8080/home and you should have SPA with 4 pages.

 

branch-name:  
Click To Copy

 

 

 

Listen for DOM changes using Mutation Observer

If we ever needed to listen for DOM events, one of the first options that comes to our mind might be to use setTimeout and periodically check the contents of the tag that we are interested in. Like in this example:

function myTest() {
    setTimeout(function() {
        console.log("Beep ...");
        if(document.getElementById('root').innerHTML == 'Loading ...') {
            myTest();
        }
        else {
            // do something 
            alert("READY!");
        }
    }, 1000);
}

But this is intense JavaScript process, which could be done using more elegant way with the help of Mutation Observer. 

Using Mutation Observer we could subscribe for different types of DOM events and trigger function on change.

example code:

<html>
  <body>    
    <div id="root">loading ...</div>

    <script>
      setTimeout(function() {
        document.getElementById('root').innerHTML = "My content is loaded!";
      }, 3000);
    </script>

    <script>
      var targetNode = document.getElementById('root');
      var config = { attributes: true, childList: true, subtree: true, characterData:true };
      var observer = new MutationObserver(function() {
        alert("#root div changed!");  
        // stop observing
        observer.disconnect();      
      });

      observer.observe(targetNode, config);
    </script>
  </body>
</html>

 what is happening here:
– created a div tag with id=’root’ and added ‘Loading …’ text.
– created MutationObserver (line 14) to listen for DOM changes in ‘root’ folder.
– specified which DOM events will trigger the function in config param (line 13)

– after 3 sec. setTimeout event fires and changes the content of the ‘root’ div, triggering DOM change event which will cause the function set in MutationObserver (line 14) to be executed.
– after showing the alert we could optionally disconnect and unsubscribe for DOM change events for this tag.

Here are some of the most used config properties

Mutation observer init properties:

At a minimum, one of childListattributes, and/or characterData must be true when you call observe(). Otherwise, a TypeError exception will be thrown.

attributeFilter Optional
An array of specific attribute names to be monitored. If this property isn’t included, changes to all attributes cause mutation notifications. No default value.
attributeOldValue Optional
Set to true to record the previous value of any attribute that changes when monitoring the node or nodes for attribute changes; see Monitoring attribute values in MutationObserver for details on watching for attribute changes and value recording. No default value.
attributes Optional
Set to true to watch for changes to the value of attributes on the node or nodes being monitored. The default value is false.
characterData Optional
Set to true to monitor the specified target node or subtree for changes to the character data contained within the node or nodes. No default value.
characterDataOldValue Optional
Set to true to record the previous value of a node’s text whenever the text changes on nodes being monitored. For details and an example, see Monitoring text content changes in MutationObserver. No default value.
childList Optional
Set to true to monitor the target node (and, if subtree is true, its descendants) for the addition or removal of new child nodes. The default is false.
subtree Optional
Set to true to extend monitoring to the entire subtree of nodes rooted at target. All of the other MutationObserverInit properties are then extended to all of the nodes in the subtree instead of applying solely to the target node. The default value is false.

Adding navigation in Single Page App

The concept of Single Page App is that all modules will be dynamically rendered while user navigates the page, without reloading. Express routing which we added is providing this functionality for us, so let’s use it.

Adding navigation.

Let’s get started. Edit index.html and add the highlighted line so we will have some marker on the page that will indicate if the page reloads.

./index.html

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

 

Create header menu for navigation.

Let’s create new navigation component and call it Header component. Create new folder ./src/components/Header and add:

./src/components/Header/index.js

import React from 'react';
import { Link } from 'react-router-dom';

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

const Header = ( {title} ) => (
  <div>
    <h2>{ title }</h2>
    <div className={styles.wrapper}>
      <ul>
        <li><Link to='/'>HOME</Link></li>
        <li><Link to='/about'>ABOUT</Link></li>
        <li><Link to='/greetings'>GREETINGS</Link></li>   
        <li><Link to='/gallery'>GALLERY</Link></li>      
      </ul>
    </div>
  </div>
);

export default Header;

also add the CSS

./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;
    
  }
}

Add header menu in our components.

Home component

We added home as a first link but we actually don’t have Home component, so let’s create a simple one and add header navigation there.

create ./src/components/Home folder and add:

./src/components/Home/index.js

import React from 'react';
import Header from '../Header';

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

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

export default Home;

and the css:

./src/components/Home/styles.js

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

About component.

do the same for About component.

./src/components/About/index.js

import React from 'react';
import Header from '../Header';

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

const About = () => (
  <div>
    <Header />
    <div className={styles.wrapper}>This is the About section.</div>
  </div>
)

export default About;

./src/components/Home/styles.scss

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

DogsCatalog container component.

and DogsCatalog container component

./src/containers/DogsCatalog/index.js

/* eslint-disable no-debugger */

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

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>
        <Header />
        <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>
      </div>
    );
  }
}


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

Greetings component.

./src/components/Greetings/index.js

import React from 'react';
import Header from '../Header';

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

const Greetings = (props) => (
  <div>
    <Header />
    <div className={styles.wrapper}><h2>Hello  {props.user}</h2></div>
  </div>
)



export default Greetings;

Set up the routes.

./src/components/app.index.js

import React, { Component } from 'react';
import Greetings from '../greetings';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import About from '../about';
import Home from '../Home';
import { ApolloProvider, graphql } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import DogsCatalog from '../../containers/DogsCatalog';
import fetch from 'unfetch';

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={Home} />    
            <Route exact path="/About" component={About} />                      
            <Route exact path="/greetings" component={Greetings} />
            <Route exact path="/gallery" component={DogsCatalog} />
          </Switch>
        </Router>
      </ApolloProvider>
    );
  }
}

Basically we told Express which component to render for each route.

Give it a try and fire up the server using your preferred config.

At this point you will have neat SPA (Single Page App) that dynamically renders the components without ever reloading the page.

 

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

 

 

Three different ways to set up the project

branch-name:  
Click To Copy

 

So far we explored setting our project using  Webpack Dev Server’s Command line Interface (CLI) Let’s explore the other two options and create scripts to run them.

Three ways of setting up Webpack

Edit package.json ‘scripts’ tag and add start script for each setup:

./package.json

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

What we just did:
– We added ‘start-cli’ script to replace ‘start’ script and to run Webpack-dev-server with Command Line Interface (the setup that we did so far)
– we added ‘start-api’ to run the project using Webpack, and Webpack-dev-server API instead.
– we added ‘startup-middleware’ to use Webpack-Dev-Server middleware.

Command line Interface (CLI)

We already have this set up and we changed the ‘start’ script tag to be ‘start-cli’ instead, but everything else is the same, but to enhance the functionality (like been able to parse scss) we will pass a config file.

./package.json

...
  "scripts": {
    "start-cli": "webpack-dev-server --hot --history-api-fallback --config webpack.cli.config.js",
...

and let’s create the cli config and since it will be the exact copy of webpack.config.js you could just rename webpack.config.js the file to be webpack.cli.config.js

./webpack.cli.config.js

const webpack = require('webpack');

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']
      }                    
    ]
  }
};

Give it a try by running yarn start-cli and everything will work like before.

Webpack Dev Server API

Create server-api.js with the following contents:

./server-api.js

const WebpackDevServer = require('webpack-dev-server');
const webpack = require('webpack');
const config = require('./webpack.api.config.js');

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

What we just did is pretty simple and it’s the same that we did for CLI but just using the APIs insted:
– we added Webpack-dev-server instance (line 1) and then we configured it with ‘hot: true’ param to enagle HMR.
– and ‘publicPath: config.output.publicPath’ which will take the parameter from the config file, and pass it to the dev server.
– we also added Webpack (line 2) which will be used as a compiler for Webpack-dev-server (line 6)

Also we have to pass a few extra parameters to webpack.config.js so let’s create a new config file, and name it:

./webpack.api.config.js

const webpack = require('webpack');

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

 

The config is almost the same like webpack.config.js with 2 extra parameters into the ”entry’ point:
– we added ‘webpack/hot/dev-server’ in order to activate HMR, and
– webpack-dev-server/client?http://localhost:8080/ specifying the client domain and port.

Also we added a plugins section and loaded HotModuleReplacementPlugin.

Webpack Dev Middleware

This is the most advanced setup and it uses Express web server with Webpack Dev Middleware. So far we used the builtin Express server from Webpack-dev-server, but now we will use standalone express server. Let’s install it first. And we will need it later not only in development mode so let’s skip ‘–dev’.

yarn add express

yarn add webpack-hot-middleware --dev

create the server file

./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 are using express server with ‘webpack-dev-middleware’,

and the config file

./webpack.middleware.config.js

const webpack = require('webpack');

module.exports = {
  mode: 'development',
  entry: [
    '@babel/polyfill',    
    './src/index.js',
    'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000',     
  ],
  output: {
    filename: '[name]-bundle.js',
    publicPath: '/dist',
  },  
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
  ],   
  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']
      }                    
    ]
  }
};

 

 Give it a try by executing yarn start-middleware and you will see the same result like in the previous two set ups. Site is running and hot module reloader is enabled.

Running server scripts using latest ES syntax

If you noticed we are using `import … from …` syntax everywhere under ./src folder instead of `const module = require(‘…’)since we set up Babel to transpile everything under this folder to the latest ES6 syntax.

But if we try to use this in some of the ./server.api.config.js or ./server.middleware.config.js it will fail since there is nothing to transpile these files to the latest ES6 yet. Fortunately there is very straight forward solution. Let’s use Babel-node to run the scripts instead of node. Like it says in the Babel-node website:

 “babel-node is a CLI that works exactly the same as the Node.js CLI, with the added benefit of compiling with Babel presets and plugins before running it.”

Perfect for our needs. Now all we need to do in order to have the benefit of EC6 in our server scripts is to replace node instance with babel-node and run scripts using babel-node instead.

But first let’s install babel-node

yarn add @babel/node --dev

then edit package.json and replace all instances of node with babel-node

./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",
...

now it should be perfectly safe to start using latest ES6 into our server scripts. We could go ahead and change all server side scripts to use import ... from syntax.

Now we have our project set up to use EC6 on the front-end and the back end as well!

Adding base Webpack config.

In the previous chapter we created three different ways of running our project:

  • Through Webpack-dev-server Command line interface (CLI) which is using `
    yarn start-cli
    which uses: webpack.cli.config.js
  • Through Webpack-dev-server API
    yarn start-api
    which uses: webpack.api.config.js
  • Through Webpack-dev-server middleware
    yarn start-middleware
    which uses: webpack.midleware.config

but if we look at the config files we will notice that most of the parameters are the same. It might make more sense if we create one base config and override only the parameters that are different in these 3 config files. Let’s do this:
– copy the contents of webpack.api.config.js and create a new file called webpack.base.config

./webpack.base.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']
      }                    
    ]
  }
};

edit all three config files to look like this:

./webpack.cli.config.js, ./webpack.api.config, ./webpack.midleware.config

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

module.exports = config;

Let’s give it a try and see if HMR still works by edit something like ./src/components/Greetings/index.js for example, and add some extra character inside the H2 tag and observe if the changes will apply

<h2>Hello, {props.user}</h2>

  • Starting with CLI
    yarn start-cli
    looks good
  • Starting with Dev server API
    yarn start-api
    looks good but HMR doesn’t work
  • Starting with Dev middleware
    yarn start-midleware
    also looks good but HMR is broken as well.

Looks like HMR works only for the CLI setup because we are not adding HotModuleReplacement plugin required for the other two set ups. Let’s fix this:

./webpack.api.config

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(),
];

module.exports = config;

./webpack.middeware.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(),
];

module.exports = config;

now give it a try and HTR should work just fine.

There is another way to achieve this by using webpack-merge module, but I personally don’t like to add extra module if I could achieve the same result with plain JavaScript.

Adding source map for easy debugging

let’s go to ./src/components/greetings/index.js  and intentionally made an error

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

 

Run the server and look at the console. You will see something similar as this:

}, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h2", null, "Hello ", props.user.test)));

This doesn’t show you where the error actually is in the original file but it shows the error in the bundle. Let’s fix this by adding ‘source-mapping‘ in our webpack.base.config

module.exports = {
  mode: 'development',
  devtool: 'source-map',

 Restart the sever and check the console: now the error points to the original file in ./src/components/greetings/index.js

make sure that before we continue you go and remove .test so the component will not break.

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

 

Adding Webpack-dev-server

branch-name:  
Click To Copy

Ading Webpack-dev-server

what is webpackdevserver ? Like it was said  “The webpackdevserver is a little Node.js Express server, which uses the webpackdev-middleware to serve a webpack bundle. It also has a little runtime script, which is connected to the server via Sock.js. The server emits information about the compilation state to the client, which reacts to those events.

Basically webpack-dev-server is handy dev tool that is not only an HTTP server but it could monitor your files for changes and rebuild the bundle and serve it from memory which speeds up the developing process.

Let’s add webpack-dev-server so we could open the project via http. Additionally  Webpack dev server adds a few more benefits like live reloading for the client side code.
Webpack dev server like it’s name suggests should be used only for development and never in production.

yarn add webpack-dev-server --dev

Let’s add a script to execute it through Yarn/NPM. Also, since we have the entry and the output set up in webpack.config.js we can simplify the build-dev and build-prod scripts as well.

  "scripts": {
    "start": "webpack-dev-server",
    "clean": "rm -rf ./dist",
    "lint": "eslint .",
    "build-dev": "webpack --mode development",
    "build-prod": "webpack --mode production"
  }

Let’s give it a try and start the server:

yarn start

and point the browser to http://localhost:8080/

If everything worked fine, you should be able to see the project and the console message.

But bundle file is still served statically from the ./dist` folder instead of bundled dynamically from Webpack-dev-server and serving it from memory,  and you could confirm this by deleting the ./dist folder, and restarting the server. And you will get this error message in the console.

GET http://localhost:8080/dist/main-bundle.js net::ERR_ABORTED 404 (Not Found)

As you see bundle file is not there, instead Webpack-dev-server is serving it from here:http://localhost:8080/main-bundle.js
Point your browser there and you will see the bundle file. Let’s fix this. Open ./webpack.config.js and add the highlighted lines:

./webpack.config.js

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

This instructs Webpack-dev-server to serve the bundle from ./dist location but this time the bundle will be dynamically generated by Webpack-dev-server, instead of statically served from the ./dist location.

The advantage is that the bundle will re-generate on change and the location will be the same as the production location but just served from the memory.
Reload the browser and now the error message in the console is gone, and if you navigate to `http://localhost:8080/dist/main-bundle.js` you will see newly generated bundle.

 Webpack-dev-server is serving the bundle from memory. You could go ahed and delete ./dist folder and reload the server and the main bundle should still be accessible.

Set up Webpack to monitor your project’s folder for changes and rebuild the bundle.

Webpack-dev-server comes with very handy option --watch to monitor for changes, rebuild the bundle and restart the browser.
The god news is that by default the --watch` mode it turned on so if you go and do some changes into any file under ./src folder and save it, you will see in the console that Webpack-dev-server will re-generate the bundles and the browser will restart and reflect the changes.

There are two ways of setting up Webpack-dev-server to reload the page:

  • Iframe mode (page is embedded in an iframe and reloaded on change)
  • Inline mode (a small webpack-dev-server client entry is added to the bundle which refreshes the page on change)

But we could go even further.

Adding Hot Module Replacement plug-in.

Having Webpack-dev-server to reload our page and reflect the changes is cool but we still reload the whole page and components state is reset back to the initial state. We could tell Webpack-dev-server to use hot module replacement plugin (HMR) to re-load only the components that did change, but before doing this, let’s make some real component that will show something in the website instead of the console.

Remove ./src/test.js.

Let’s create greeting component that will simply show a greeting in the webpage.
create folder called ‘greeting’ and file in ./src/greeting/index.js with these contents:

./src/greeting/index.js

function doGreeting(name) {
  return "Hello " + name;
}
module.exports = doGreeting;

edit ./src/app.js to load the new component and attach the result to an HTML div component. We have to also add the highlighted if statement that will enable HMR.

./src/app.js

var greeting = require('./greeting');

var result = greeting("John");
document.querySelector('#root').innerHTML = result;


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

and edit index.html to have a div component with id=”root” that  will be the entry point for our app and will show the result of our greeting component.

./index.html

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

Last step is to tell Webpack-dev-server to use HMR.

./package.json

{
  "name": "babel-webpack-react-boilerplate",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "@babel/core": "^7.1.2",
    "@babel/preset-env": "^7.1.0",
    "@babel/preset-react": "^7.0.0",
    "babel-eslint": "^10.0.1",
    "babel-loader": "^8.0.4",
    "eslint": "^5.7.0",
    "eslint-loader": "^2.1.1",
    "eslint-plugin-react": "^7.11.1",
    "webpack": "^4.20.2",
    "webpack-cli": "^3.1.2",
    "webpack-dev-server": "^3.1.9"
  },
  "scripts": {
    "start": "webpack-dev-server --hot",
    "clean": "rm -rf ./dist",
    "lint": "eslint .",
    "build-dev": "webpack --mode development",
    "build-prod": "webpack --mode production"
  }
}

what we just did:
– we added --hot parameter to tell Webpack-dev-server to use HMR.

Go ahead, restart the server and give it a try and now if you change something in ./src folder the browser will reflect the changes without reloading. (you could observe the reload button and it will never show reload). Also if you look at browser’s console you will see the messages from HMR

[HMR] Waiting for update signal from WDS…
client:71 [WDS] Hot Module Replacement enabled.

Now development process could really speed up!

branch-name:  
Click To Copy