React And Bootstrap
There’s a very small chance you haven’t heard about React or Bootstrap.
Both React and Bootstrap, are very very popular. In fact, they are so popular that there’s a project that combines them.
Since React is so popular, I decided I should be more familiar with it and also thought it would be nice to share my learnings.
In this example, we will create a Bootstrap navbar without “react-bootstrap”. The complete solution is on github and you can get it here.
Prerequisites
I’ll assume you are comfortable with HTML, Bootstrap, css and javascript. Entry level knowledge of React is required.
Step 1 - Setup
If you don’t have React installed, install it now by following the instructions on the React website.
Create a new React app:
$ create-react-app react-bootstrap-navbar-example
This generated some files for us in the react-bootstrap-navbar-example folder.
Great! Now, let’s add Bootstrap to our new generated index.html file. The easiest way to do this is with the CDN, as documented here.
Add the CDN link to the head section of index.html:
public/index.html
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<!-- Bootsrap! -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<title>React App</title>
</head>
Note that for this example, Bootstrap’s javascript is not required.
We are going to use the ‘react-router-dom’ package, so go ahead and install it.
In your project dir:
$ npm install react-router-dom --save
Start the React development server from the project folder like this:
$ npm start
Step 2 - Add Navigation Links
We are going to have two sections:
- The “main” section that will hold our content.
- The “header” section that will hold our navbar.
Go ahead and replace the contents of src/App.js with this code:
import React, { Component } from 'react';
const Main = () => (
<div>
main!
</div>
)
const Header = () => (
<div>
header!
</div>
)
const App = () => (
<div>
<Header />
<Main />
</div>
)
export default App;
As you can see, it doesn’t do much yet. It just divides our page to two sections. If you point your browser to localhost:3000, you should see the words “header!” and “main!” one on top of the other.
Let’s continue by adding our pages. Since this is only an example, they won’t be too impressive. Create a new file for our pages: src/pages.js
import React, { Component } from 'react';
export const Home = () => (
<div>
<h1>Home</h1>
</div>
)
export const Page1 = () => (
<div>
<h1>Page1</h1>
</div>
)
export const Page2 = () => (
<div>
<h1>Page2</h1>
</div>
)
export const Page3 = () => (
<div>
<h1>Page3</h1>
</div>
)
Change Our App in index.js to be a BrowserRouter:
src/index.js
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render((
<BrowserRouter>
<App />
</BrowserRouter>
), document.getElementById('root'))
Change the Main component in App.js to a Switch:
src/App.js
import { Switch, Route, Link } from 'react-router-dom'; // import the react-router-dom components
import { Home, Page1, Page2, Page3 } from './pages' // import our pages
const Main = () => (
<main>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/1' component={Page1}/>
<Route exact path='/2' component={Page2} />
<Route exact path='/3' component={Page3} />
</Switch>
</main>
)
Change our Header component to show links: src/App.js
const Header = () => (
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/1">Page1</Link>
</li>
<li>
<Link to="/2">Page2</Link>
</li>
<li>
<Link to="/3">Page3</Link>
</li>
</ul>
</div>
)
If you want to read more about React routers, there’s a nice tutorial about it here.
Alright! So we now have a functional, ugly website with routing.
Step 3 - Adding Bootstrap
Let’s turn our ugly header to a Bootstrap navbar. Here’s a link to the navbar documentation, in case you want to know more.
Change the Header component in App.js:
src/App.js
const Header = () => (
<div>
<nav className="navbar navbar-expand-lg navbar-light bg-light">
<Link className="navbar-brand" to="/">Home</Link>
<ul className="navbar-nav">
<li className="nav-item">
<Link className="nav-link" to="/1">Page1</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/2">Page2</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/3">Page3</Link>
</li>
</ul>
</nav>
</div>
)
Here is how it should look like:
Looks so much better!
Step 4 - Use Bootstrap’s Active Class
Our page looks better already, but we would also want our links to appear as active in respect to the page we are on. To achieve this, we will turn our links to React components.
Add the code for our new NavLink component in App.js:
src/App.js
class NavLink extends Component {
render() {
return (
<li className="nav-item" >
<Link className="nav-link" to={this.props.path}>{this.props.text}</Link>
</li>
);
}
}
Our NavLink will get the path and text as part of it’s passed properties.
Change our Header to use the new NavLink:
src/App.js
const Header = () => (
<div>
<nav className="navbar navbar-expand-lg navbar-light bg-light">
<Link className="navbar-brand" to="/">Home</Link>
<ul className="navbar-nav">
<NavLink path="/1" text="Page 1" />
<NavLink path="/2" text="Page 2" />
<NavLink path="/3" text="Page 3" />
</ul>
</nav>
</div>
)
This will look exactly the same in the browser, but since we turned our simple html to a component, our code already looks nicer.
Let’s add the active functionality to NavLink:
src/App.js
class NavLink extends Component {
render() {
return (
<li className={"nav-item " + (this.props.isActive ? "active": "")}>
<Link className="nav-link" to={this.props.path}>{this.props.text}</Link>
</li>
);
}
}
Our NavLink will now render with the active class, in case we pass isActive=true
to it.
In React, when you have multiple stateful child components, it is better to “lift the state upwards”.
From the React tutorial:
When you want to aggregate data from multiple children or to have two child components communicate with each other, move the state upwards so that it lives in the parent component. The parent can then pass the state back down to the children via props, so that the child components are always in sync with each other and with the parent.
Let’s change our Header component so it could handle it’s children’s state:
src/App.js
class Header extends Component {
constructor(props) {
super(props);
this.state = {
links: [
{path: "/1", text: "Page 1", isActive: false},
{path: "/2", text: "Page 2", isActive: false},
{path: "/3", text: "Page 3", isActive: false},
]
}
}
handleClick(i) {
const links = this.state.links.slice();
for (const j in links) {
links[j].isActive = i == j ;
}
this.setState({links: links});
}
render() {
return (
<div>
<nav className="navbar navbar-expand-lg navbar-light bg-light">
<Link className="navbar-brand" to="/">Home</Link>
<ul className="navbar-nav">
{this.state.links.map((link, i) =>
<NavLink
path={link.path}
text={link.text}
isActive={link.isActive}
key={link.path}
onClick={() => this.handleClick(i)}
/>
)}
</ul>
</nav>
</div>
);
}
}
Add onClick to our NavLink, so the parent can pass it:
App.js
class NavLink extends Component {
render() {
return (
<li className={"nav-item " + (this.props.isActive ? "active": "")}>
<Link
className="nav-link"
to={this.props.path}
onClick={() => this.props.onClick()}
>
{this.props.text}</Link>
</li>
);
}
}
Great! Now when you click on one of your links, the active class would be added. It should look like this:
Summary
So… What did we do here?
We created a React router, with header and main sections.
We then added Bootstrap, making our website look nicer.
Finally we created our own NavLink component and saw how to “lift up the state”.
I am sure that there are many solutions to this very common problem. Some of them might be less verbose, and even solve it in a line or two.
I hope you enjoyed, and of course, feel free to comment or share.
Thank you for reading.