React Virtual DOM and React Router
THE BROWSER DOM
Before we talk about the React Virtual DOM. We will refresh on the concept of the browser DOM.
DOM is short for Document Object Model
The Elements tab is a representation of the DOM. The elements tab in the developer tools window gives us a representation of the DOM in real time. But the DOM is not HTML nor is it JavaScript. It acts as an interface between the two. The DOM is an API (application programming interface) and it is a set of specifications on how programming languages and HTML can interact with each other. It MAPS HTML elements to a node-based tree-like representation of HTML behind the scenes. An interface that programming languages can easily understand, traverse and modify.
You see here an example of HTML and representation in the DOM. This representation is created by the browser using a technology called a browser engine. Every time a web page is loaded your browser creates this abstract model and holds it in memory. The DOM is the interface that makes it possible for you to refer to HTML elements and attributes using JavaScript object and property syntax.
So you might create a new image element node in JavaScript like this and then you could insert this node into the DOM using a method like append child. And it would finally be rendered by the browser into this HTML element.
Again, to repeat, there is an interface between a programming language like JavaScript and the actual code that gets rendered in the browser with HTML and CSS, and the DOM is that interface. It is cross-platform and language-independent, so it can be used in all operating systems and you can interact with it using other languages not just JavaScript, though the vast majority of the time it will be JavaScript.
Whenever you cause any changes to a webpages view with JavaScript, then the affected nodes and their children in the browser DOM are recreated. Along with that, the browser view is also re-rendered graphically and that can be an expensive process.
- Cross-platform and language-independent- not only used with JavaScript(though usually so)
- Any changes you make with JavaScript to a webpage’s view causes the DOM to be recreated – the affected DOM nodes and all their children, the entire “branch”
- Whenever the browser DOM is recreated, then the graphical view is also re-rendered, which can be an expensive process
Now we can talk about the virtual DOM and why it exists.
THE VIRTUAL DOM
The virtual DOM technique is not unique to React – React is one implementation. The React application maintains a lightweight representation of the browser DOM in memory. It is much faster and easier to update the Virtual DOM than the browser DOM – no-rendering of the graphical display. Any DOM changes you make with React are first made to the Virtual DOM. So if the user clicks on a button and it changes the color of the web page’s background. For example instead of manipulating the real browser DOM directly. This change first gets made in React Virtual DOM.
When changes are made to the Virtual DOM. React compares the changes during a process called reconciliation, which is done through it’s reconciliation engine named FIBER. In this process it looks for which nodes have changes in the DOM tree and then it figures out the most optimized way tp patch the browser down with the changes and to minimize the amount of re-rendering, while applying the changes. For example, by sending multiple changes in a combined batch instead of one at a time.
- React uses a process called reconciliation to compare and find differences between (“diff”) the Virtual DOM and the browser DOM.
- Sophisticated reconciliation engine named Fiber.
- Looks for what’s changed, then figures out the most optimized way to patch the browser DOM with the changes with minimal re-rendering, such as by combining updates in batches instead of multiple changes.
This is the main reason why you need to use setState for any changes in React, instead of setting the state directly with an assignment operator. setState sends your change through the virtual DOM. So if you don’t use it, then you are sidestepping one of the most useful features of React.
You also learned before about setting unique key attributes when rendering lists of React elements using MAP before. When doing so, you are helping out the React reconciliation process by giving it stable markers to track and look for when changes have been made, so it does not re-render unchanged items unnecessarily.
Header and Footer
- On terminal: yarn add font-awesome@4.7.0 or npm install font-awesome@4.7.0
- On terminal yarn add bootstrap-social@5.1.1 or npm install bootstrap-social@5.1.1
Make changes to these files with this code:
index.js
import React from ‘react’;
import ReactDOM from ‘react-dom’;
import ‘./index.css’;
import reportWebVitals from ‘./reportWebVitals’;
import ‘bootstrap/dist/css/bootstrap.min.css’;
import ‘bootstrap/dist/css/bootstrap.min.css’;
import ‘typeface-lobster’;
import ‘typeface-open-sans’;
import ‘font-awesome/css/font-awesome.css’;
import ‘bootstrap-social/bootstrap-social.css’;
import App from ‘./App’;
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById(‘root’)
);
HeaderComponent.js
import React, { Component } from ‘react’;
import { Navbar, NavbarBrand, Jumbotron } from ‘reactstrap’;
class Header extends Component {
render() {
return (
<React.Fragment>
<Jumbotron fluid>
<div className=”container”>
<div className=”row”>
<div className=”col”>
<h1>NuCamp</h1>
<h2>a better way to camp</h2>
</div>
</div>
</div>
</Jumbotron>
<Navbar dark sticky=”top”>
<div className=”container”>
<NavbarBrand href=”/”>NuCamp</NavbarBrand>
</div>
</Navbar>
</React.Fragment>
);
}
}
export default Header;
FooterComponent.js
import React from ‘react’;
function Footer(props) {
return (
<footer className=”site-footer”>
<div className=”container”>
<div className=”row”>
<div className=”col-4 col-sm-2 offset-1″>
<h5>Links</h5>
<ul className=”list-unstyled”>
<li><a href=”#”>Home</a></li>
<li><a href=”#”>Directory</a></li>
<li><a href=”#”>About</a></li>
<li><a href=”#”>Contact</a></li>
</ul>
</div>
<div className=”col-6 col-sm-3 text-center”>
<h5>Social</h5>
<a className=”btn btn-social-icon btn-instagram” href=”http://instagram.com/”><i className=”fa fa-instagram” /></a>{‘ ‘}
<a className=”btn btn-social-icon btn-facebook” href=”http://www.facebook.com/”><i className=”fa fa-facebook” /></a>{‘ ‘}
<a className=”btn btn-social-icon btn-twitter” href=”http://twitter.com/”><i className=”fa fa-twitter” /></a>{‘ ‘}
<a className=”btn btn-social-icon btn-google” href=”http://youtube.com/”><i className=”fa fa-youtube” /></a>
</div>
<div className=”col-sm-4 text-center”>
<a role=”button” className=”btn btn-link” href=”tel:+12065551234″><i className=”fa fa-phone” /> 1-206-555-1234</a><br />
<a role=”button” className=”btn btn-link” href=”mailto:notreal@notreal.co”><i className=”fa fa-envelope-o” /> campsites@nucamp.co</a>
</div>
</div>
</div>
</footer>
);
}
export default Footer;
MainComponent.js
import React, { Component } from ‘react’;
import Directory from ‘./DirectoryComponent’;
import CampsiteInfo from ‘./CampsiteInfoComponent’;
import Header from ‘./HeaderComponent’;
import Footer from ‘./FooterComponent’;
import { CAMPSITES } from ‘../shared/campsites’;
class Main extends Component {
constructor(props) {
super(props);
this.state = {
campsites: CAMPSITES,
selectedCampsite: null
};
}
onCampsiteSelect(campsiteId) {
this.setState({selectedCampsite: campsiteId});
}
render() {
return (
<div>
<Header />
<Directory campsites={this.state.campsites} onClick={campsiteId => this.onCampsiteSelect(campsiteId)}/>
<CampsiteInfo campsite={this.state.campsites.filter(campsite => campsite.id === this.state.selectedCampsite)[0]} />
<Footer />
</div>
);
};
}
export default Main;
App.css
body {
font-family: “Open Sans”, sans-serif;
}
h1, h2, h3, .card-body>.card-title {
font-family: “Lobster”, cursive;
}
.card-title {
font-weight: 500;
font-size: 1.25rem;
}
h2 {
color: #3046C5;
}
.card-img-overlay .card-title {
color: white;
background-color: rgba(0, 0, 0, 0.5);
}
.row-content {
padding: 50px 0;
border-bottom: 1px ridge;
min-height: 200px;
}
.site-footer {
background-image: linear-gradient(#CEC8FF, white);
padding: 20px 0;
}
.jumbotron {
margin: 0;
padding: 30px;
background-image: linear-gradient(to right, #5637DD, #3046C5, #60106B);
color: white;
}
.jumbotron h2 {
color: #CEC8FF;
}
.navbar-dark {
background-color: #901CAD;
Final Output at this stage should look like this:
Overview: React Router
A big difference from Bootstrap and how you approach your React app includes not having different HTML pages for each unique view on your site. Instead you will have just one webpage index.html and from there you will use the React Library to create an application that is launched from that one page. This is one of the core features of a single page application (SPA). A routing library such as React Router is crucial to an SPA.
React Router is a popular third-party library in the React Ecosystem. It provides a collection of router components, route matching components, and navigation components. And it handles routing request for different views within our app. It is also helpful such as: generating unique URLS that can be booked marked. It also handles going back and forward in the browser history.
React Router Components
Some of the common components you will use in React Router include:
- <BrowserRouter> – Top-level parent component that wraps around all other React-Router components. It uses HTML5 history API to control browser history
- <Route> – Renders the UI for a matching path. Use exact attribute if you want an exact match
- <Redirect> – Redirects to a new URL
- <Switch> – Groups <Route> components together
- <Link> – Creates links to a path, renders as <a> – use instead of <a>!
- <NavLink> – Special version of <Link>, adds styles to active link
React Router
- On terminal: yarn add react-router-dom@5.2.0 or npm install react-router-dom@5.2.0
Make changes to these files with this code:
App.js
import React, { Component } from ‘react’;
import Main from ‘./components/MainComponent’;
import { BrowserRouter } from ‘react-router-dom’;
import ‘./App.css’;
class App extends Component {
render() {
return (
<BrowserRouter>
<div className=”App”>
<Main />
</div>
</BrowserRouter>
);
};
}
export default App;
HomeComponent.js
import React from ‘react’;
function Home(props) {
return (
<div className=”container”>
<h4>Home</h4>
</div>
);
}
export default Home;
MainComponent.js
import React, { Component } from ‘react’;
import Directory from ‘./DirectoryComponent’;
import CampsiteInfo from ‘./CampsiteInfoComponent’;
import Header from ‘./HeaderComponent’;
import Footer from ‘./FooterComponent’;
import Home from ‘./HomeComponent’;
import { Switch, Route, Redirect } from ‘react-router-dom’;
import { CAMPSITES } from ‘../shared/campsites’;
class Main extends Component {
constructor(props) {
super(props);
this.state = {
campsites: CAMPSITES
};
}
render() {
const HomePage = () => {
return (
<Home />
);
};
return (
<div>
<Header />
<Switch>
<Route path=’/home’ component={HomePage} />
<Route exact path=’/directory’ render={() => <Directory campsites={this.state.campsites} />} />
<Redirect to=’/home’ />
</Switch>
<Footer />
</div>
);
}
}
export default Main;
HeaderComponent.js
import React, { Component } from ‘react’;
import { Nav, Navbar, NavbarBrand, NavbarToggler, Collapse, NavItem, Jumbotron } from ‘reactstrap’;
import { NavLink } from ‘react-router-dom’;
class Header extends Component {
constructor(props) {
super(props);
this.toggleNav = this.toggleNav.bind(this);
this.state = {
isNavOpen: false
};
}
toggleNav() {
this.setState({
isNavOpen: !this.state.isNavOpen
});
}
render() {
return (
<React.Fragment>
<Jumbotron fluid>
<div className=”container”>
<div className=”row”>
<div className=”col”>
<h1>NuCamp</h1>
<h2>a better way to camp</h2>
</div>
</div>
</div>
</Jumbotron>
<Navbar dark sticky=”top” expand=”md”>
<div className=”container”>
<NavbarBrand className=”mr-auto” href=”/”><img src=”/assets/images/logo.png” height=”30″ width=”30″ alt=”NuCamp Logo” /></NavbarBrand>
<NavbarToggler onClick={this.toggleNav} />
<Collapse isOpen={this.state.isNavOpen} navbar>
<Nav navbar>
<NavItem>
<NavLink className=”nav-link” to=”/home”>
<i className=”fa fa-home fa-lg” /> Home
</NavLink>
</NavItem>
<NavItem>
<NavLink className=”nav-link” to=”/directory”>
<i className=”fa fa-list fa-lg” /> Directory
</NavLink>
</NavItem>
<NavItem>
<NavLink className=”nav-link” to=”/aboutus”>
<i className=”fa fa-info fa-lg” /> About
</NavLink>
</NavItem>
<NavItem>
<NavLink className=”nav-link” to=”/contactus”>
<i className=”fa fa-address-card fa-lg” /> Contact Us
</NavLink>
</NavItem>
</Nav>
</Collapse>
</div>
</Navbar>
</React.Fragment>
);
}
}
export default Header;
FooterComponent.js
import React from ‘react’;
import { Link } from ‘react-router-dom’;
function Footer(props) {
return (
<footer className=”site-footer”>
<div className=”container”>
<div className=”row”>
<div className=”col-4 col-sm-2 offset-1″>
<h5>Links</h5>
<ul className=”list-unstyled”>
<li><Link to=’/home’>Home</Link></li>
<li><Link to=’/directory’>Directory</Link></li>
<li><Link to=’/aboutus’>About</Link></li>
<li><Link to=’/contactus’>Contact</Link></li>
</ul>
</div>
<div className=”col-6 col-sm-3 text-center”>
<h5>Social</h5>
<a className=”btn btn-social-icon btn-instagram” href=”http://instagram.com/”><i className=”fa fa-instagram” /></a>{‘ ‘}
<a className=”btn btn-social-icon btn-facebook” href=”http://www.facebook.com/”><i className=”fa fa-facebook” /></a>{‘ ‘}
<a className=”btn btn-social-icon btn-twitter” href=”http://twitter.com/”><i className=”fa fa-twitter” /></a>{‘ ‘}
<a className=”btn btn-social-icon btn-google” href=”http://youtube.com/”><i className=”fa fa-youtube” /></a>
</div>
<div className=”col-sm-4 text-center”>
<a role=”button” className=”btn btn-link” href=”tel:+12065551234″><i className=”fa fa-phone” /> 1-206-555-1234</a><br />
<a role=”button” className=”btn btn-link” href=”mailto:notreal@notreal.co”><i className=”fa fa-envelope-o” /> campsites@nucamp.co</a>
</div>
</div>
</div>
</footer>
);
}
export default Footer;
DirectoryComponent.js
import React from ‘react’;
import { Card, CardImg, CardImgOverlay, CardTitle } from ‘reactstrap’;
function RenderDirectoryItem({campsite}) {
return (
<Card>
<CardImg width=”100%” src={campsite.image} alt={campsite.name} />
<CardImgOverlay>
<CardTitle>{campsite.name}</CardTitle>
</CardImgOverlay>
</Card>
);
}
function Directory(props) {
const directory = props.campsites.map(campsite => {
return (
<div key={campsite.id} className=”col-md-5 m-1″>
<RenderDirectoryItem campsite={campsite} />
</div>
);
});
return (
<div className=”container”>
<div className=”row”>
{directory}
</div>
</div>
);
}
export default Directory;
Final Output at this stage should look like this:
Additional Resources: