Using Context and HashRouter in React

We will create React app which will use context and HashRouter. The will fetch data from API and supply all app with data. It is an easy way to create an app without the backend using free API as a data source.

Working project https://www.react-routes.ct8.pl/

In this level, you should know how to create react app and knowledge from reactjs.org main concept but it’s not necessary if you are familiar with javascript.

As an API we will use restcountries.eu version v2 which have country flag images for better performance.

The basic concept of our App:

Brak alternatywnego tekstu dla tego zdjęcia


This diagram shows us how our context will provide data across all components.

Let’s dive into it and break on parts:

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './App.css';
import { HashRouter, Route } from "react-router-dom";
import { Home, About, Detail } from "./components/pages"
import * as serviceWorker from './serviceWorker';
import { BasicProvider } from './BasicContext';
ReactDOM.render(
<HashRouter>
<BasicProvider>
<Route exact path="/" component={Home} />
<Route path="/about" component={About}/>
</BasicProvider>
</HashRouter>
, document.getElementById('root'));
serviceWorker.unregister();

In our index.js we import our components and BasicProvider which wrap our Route to supply it in data. All are pass to render function.

BasicContext logic:

BasicContext.js
import React, { Component } from "react";
const BasicContext = React.createContext();
class BasicProvider extends Component{
constructor(props){
super(props);
this.state = {
username: 'user',
loading:false
};
}
componentWillMount(){
this.setState({ loading: true });
fetch("https://restcountries.eu/rest/v2/all")
.then(response => response.json())
.then(response => {
this.setState({ 
exampleItems: response, 
loading: false,
initialData: response });
console.log(this.state);
});
}
render(){
const { loading } = this.state
return(
loading === false ?
<BasicContext.Provider value={ this.state }>
{this.props.children}
</BasicContext.Provider>
: <h1>Loading</h1>
)
}
}
const BasicConsumer = BasicContext.Consumer;
export { BasicProvider, BasicConsumer };

In BasicProvider we fetch the data from API and set it to state. Next, in the render function, we check if the data successfully fetched and assign this.state as a value to BasicContext which is defined as a context. Provider which wrap everything which is pass to this component. In our examples we wrap Routes. But the value we use deeper using consumer. Lastly, we create BasicConsumer which we will use to pass a value to the nested component.

Next step is to see how our home and about components are built:

pages.js
import React from "react";
import { MainMenu } from "./menu";
import CountryList from "./CountryList";
import Services from "./Services";
import { BasicConsumer } from "../BasicContext";

class PageTemplate extends React.Component{
constructor(props){
super(props);
}
render(){
return(
<div className="Page">
<MainMenu />
{this.props.children}
</div>
)
}
}
export const Home = () => (
<PageTemplate>
<BasicConsumer>
{({ initialData,exampleItems }) => 
<CountryList test={initialData} test2={exampleItems} />}
</BasicConsumer>
</PageTemplate>
)
export const About = () => (
<PageTemplate>
<BasicConsumer>
{({ initialData,exampleItems }) =>
<Services test={initialData} test2={exampleItems} />}</BasicConsumer>
</PageTemplate>
)

To display navigation in our app we create PageTemplate component which renders our menu component and everything which is pass to it. In Home and About component we use PageTemplate component to have navigation next we use BasicConsumer which keep our state from BasicContext.js. Using deconstructor we use from our state initialData and exampleItems value. Which will provide CountryList and Services component in data. This example shows how helpful can be context and can pass data deep in the nested component without worrying about props.

Next, lest’s see how our navigation looks like.

import React from "react";
import { NavLink } from "react-router-dom";
export const MainMenu = () =>(
<nav className="navbar navbar-expand-lg navbar-light bg-light">
<ul className="navbar-nav">
<li className="nav-item active">
<NavLink className="nav-link" to="/">Home <span className="sr-only">(current)</span></NavLink>
</li>
<li className="nav-item">
<NavLink className="nav-link" to="/about">Facts</NavLink>
</li>
</ul>
</nav>
);

Here is logic for our navigation basic navLink with to parameter which will work with our Routes define in index.js.

Next, let’s see our CountryList component:

import React, {Component} from "react";
import Land from './Land';
import Pagination from './Pagination';
export default class CountryList extends Component{
constructor(props){
super(props)
this.state = {
initialData:this.props.test,
exampleItems: this.props.test2,
pageOfItems: [],
loading: false,
}
this.onChangePage = this.onChangePage.bind(this);
this.filterList = this.filterList.bind(this);
this.filterContinents = this.filterContinents.bind(this);
}
onChangePage(pageOfItems) {
// update state with new page of items
this.setState({ pageOfItems: pageOfItems });
console.log(this.state);
}
filterList(event) {
var updatedList = this.state.initialData;
updatedList = updatedList.filter(function(item) {
return item.name.toLowerCase().search(event.target.value.toLowerCase()) !== -1;
});
this.setState({ exampleItems: updatedList });
}
filterContinents(event) {
var updatedList = this.state.initialData;
if(event.target.value.toLowerCase() === "all region"){
this.setState({ exampleItems: updatedList });
}else{
updatedList = updatedList.filter(function(item) {
return item.region.toLowerCase().search(event.target.value.toLowerCase()) !== -1;
});
this.setState({ exampleItems: updatedList });
}
}
render(){
const { exampleItems, pageOfItems} = this.state
return (
<div className="container">
<div className="row">
<div className="col-sm-12">
<form>
<div className="form-group">
<label>Search for Country:</label>
<input type="email" className="form-control" placeholder="Search" onChange​={this.filterList}/>
</div>
<div className="form-group">
<label>Search by contynent:</label>
<select className="form-control" onChange​={this.filterContinents}><option>All region</option>
<option>Polar</option>
<option>Africa</option>
<option>Europe</option>
<option>Asia</option>
<option>Americas</option>
<option>Oceania</option>
</select>
</div>
</form>
</div>
</div>
<div className="row">
{
pageOfItems.map((item,index) => {
return <Land key={index} nation={item} delay={index}/>})
}
</div>
<Pagination
items={exampleItems}
onChangePage​={this.onChangePage}
/>
</div>
)
}
}

CountryList component allowed us to display data with pagination and sort it in two way. By continent and by search field. In this example, I use the universal component create by react community with all logic to paginate data with bootstrap styles. To filter data I use two arrays of our data one is as a base to work on it and another does display data. In this scenario, clean data is always stored and doesn’t change.

Let’s see the Land component:

import React, {Component} from "react";
import Modal from './Modal';
export default class Land extends Component {
constructor(props){
super(props)
this.state = {
nation:this.props.nation,
showModal:false,
}
this.handleShow = this.handleShow.bind(this);
this.handleHide = this.handleHide.bind(this);
}
componentWillReceiveProps(nextProps) {
this.setState({ nation: nextProps.nation});
}
handleShow(){
this.setState({ showModal: true });
}
handleHide(){
this.setState({ showModal:false });
}
render(){
const{ nation,delay } = this.state
const time = delay * 100;
const modal = this.state.showModal ? (
<Modal>
<div className="modal" tabIndex="-1" role="dialog">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">Country Details</h5>
<button onClick={this.handleHide} type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div className="modal-body">
<p>{nation.name}</p>
<p>Capita: {nation.capital}</p>
<p>Region: {nation.region}</p>
<p>Subregion: {nation.subregion}</p>
<p>Population: {nation.population} people</p>
<p>TopLevelDomain: {nation.topLevelDomain.map(domain => domain).join(", ")}</p>
<p>Timezones: {nation.timezones[0]}</p>
<p>Languages: {nation.languages.map(lang => lang.name + ` ( ${lang.nativeName} )`).join(", ")}</p>
<p>Native name: {nation.nativeName}</p>
<p>Translations:</p>
<p>[es]: {nation.translations['es']}</p>
<p>[ja]: {nation.translations['ja']}</p>
<p>[fa]: {nation.translations['fa']}</p>
<p>Currencies: {nation.currencies.map(curr => `${curr.name} - ${curr.code} (${curr.symbol})`)}</p>
<p></p>
</div>
<div className="modal-footer">
<button onClick={this.handleHide} type="button" className="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</Modal>
) : null;
return (
<div className="col-md-4"  style={{transitionDelay: `${time}ms`}} ><div className="card">
<div className="card-body">
<h5 className="card-title">{this.props.nation.name}</h5>
<h6 className="card-subtitle mb-2 text-muted">Capital: {this.props.nation.capital}</h6>
</div>
<img src={this.props.nation.flag} className="card-img-top" alt="..."/>
<ul className="list-group list-group-flush">
<li className="list-group-item">Region: {this.props.nation.region}</li>
<li className="list-group-item">Subregion: {this.props.nation.subregion}</li>
</ul>
<div className="card-body">
<button type="button" className="btn btn-outline-primary" onClick={this.handleShow}>Show more</button>
</div>
</div>
{modal}
</div>
)
}
}

This is a standard component to display data one interesting thing is modal which uses react portal to display modal window and hide it on the onclick event.

Let’s see how the modal component looks like:

import React from "react";
import ReactDOM from "react-dom";
const modalRoot = document.getElementById('modal-root');
export default class Modal extends React.Component{
constructor(props){
super(props);
this.el = document.createElement('div');
}
componentDidMount(){
modalRoot.appendChild(this.el);
}
render(){
return ReactDOM.createPortal(
this.props.children,
this.el,
);
}
}

Here we create div element which will show modal and assign as a value all that will be pass to this component. We must remember to create in an index.html div element with id modal-root as a reference to modal div.

Next, let’s see Services component:

import React from "react";
import Areas from './countryFacts/Areas';
import Population from './countryFacts/Population';

export default class Services extends React.Component{
constructor(props){
super(props);
this.state = {
initialData:this.props.test,
exampleItems: this.props.test2,
loading: false,
}
}
render(){
const { initialData } = this.state
return(
<div className="container">
<Areas initialData={initialData}/>
<Population initialData={initialData}/>
</div>
)
}
}

In this component, we have 2 nested component Areas and Population which will show detail information about our data.

import React from "react";
import { compareValues } from "../../utils/filter";
import { Link } from 'react-router-dom';

export default class Areas extends React.Component{
constructor(props){
super(props);
this.state = {
area:[],
initialData : this.props.initialData,
}
}
componentWillMount(){
var updatedList = this.state.initialData;
updatedList = updatedList.sort(compareValues('area','desc')).slice(0,16).map((item,index) =>{
return {
area:item.area,
name:item.name,
}
}
)
this.setState({ area:updatedList })
}
render(){
const { area } = this.state
return(
<div className="mt-5 mb-5">
<h2>Most areas Country on Earth:</h2>
<ul className="list-group">
{
area ?  area.map((item,index) => {
return <li className="list-group-item" key={index}>
<p>{item.name}</p> area: {item.area.toLocaleString()}
</li>
}) : <h1>Loading</h1>}
</ul>
</div>
)
}
}

The interesting thing in this component is a function which compares and sort values depends on parameters set to it. The function looks like this:

utils/filter.js
export const compareValues =  function compareValues(key, order='asc'){
return function(a,b){
if((!a.hasOwnProperty(key)) || !b.hasOwnProperty(key)){
return 0;
}
const varA = (typeof a[key] === 'string') ?
a[key].toUpperCase() : a[key];
const varB = (typeof b[key] === 'string') ?
b[key].toUpperCase() : b[key];
let comparison = 0;
if(varA > varB){
comparison = 1;
}else if (varA < varB){
comparison = -1;
}
return(
(order === 'desc') ? (comparison * -1) : comparison
)}
}

It reacts community utils which allowed sort object in define way. When we don’t have many tasks and not familiar with the package like sugar it can solve most development problem.

Last part of our small app is Population component:

import React from "react";
import { compareValues } from "../../utils/filter";
export default class Population extends React.Component{
constructor(props){
super(props);
this.state = {
population:[],
initialData: this.props.initialData
}
}
componentDidMount(){
var updatedList = this.state.initialData;
updatedList = updatedList.sort(compareValues('population','desc')).slice(0,16).map((item) =>{
return {
population:item.population,
name:item.name
}
}
)
this.setState({ population:updatedList })
}
render(){
const { population } = this.state
return(
<div className="mt-5 mb-5">
<h2>Most people in Country on Earth:</h2>
<ul className="list-group">
{
population.map((item,index) => {
return <li className="list-group-item" key={index}><b>{item.name}</b> population: {item.population.toLocaleString()}</li>
}
)}
</ul>
</div>
)
}
}

It’s quite similar to Areas component.

That’s how can look like ready to deploy project remember to add to package.json “homepage”:”.”. This will set the root structure in a proper way. Next npm build and transfer the file to a domain and we can see our project on the web. Project is in bitbucket repo https://bitbucket.org/tetsumote/country-app-2/src/master/ where are more adjust like transition and detail page which are not discussed in this article.

Thanks for attention.

To view or add a comment, sign in

More articles by Paweł Grajewski

  • React Native todo app

    Useful links: https://facebook.github.

  • Splice array prototype

    When we want to add or remove elements from the array best way to do this is to apply splice or shift method. First…

  • Using spread operator to store Form values in React

    Storing data from the form is a common task in React. Very useful for this task is javaScript spread operators and…

  • Arrow function playground

    What can be more useful than a shortcut in programming languages? An arrow function is one of these things which can be…

  • React fetch data and manipulate the response

    In this article, we will use react and fetch API to show how to display multi data with one REST API call. To save time…

  • Online sandbox code editor for fast prototyping

    Creating a project takes time. We must adjust the virtual environment or add npm packages to the project.

  • React static page with React-app and Reactstrap

    In this tutorial, we will learn how to use React react-app and dedicated version of bootstrap for react Reactstrap. At…

    1 Comment
  • Django and Selenium

    Selenium is a project which aim is to test site with webdriver. All most popular browser has to develop webdriver which…

  • Deploy Django project on Heroku

    This story will be about how to build basic project in Django and deploy it in Heroku. It can be good practice for…

Explore content categories