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.

 

Leave a Reply