Bootstrapping a react app

with bootstrap and font awesome

Published April 7, 2019 #howto #react #bootstrap #javascript

Here’s a quick recipe for getting a blank react project with bootstrap up and running. We’ll walk though all of the steps that you’ll need to get a basic bootstrap based framework up and running, ready for theming and component implementation using redux.

Steps

Install the following packages.

Install node/nvm

If you don’t have it

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash
source ~/.profile
nvm install 10
nvm global 10

Install yarn

If you are into that

npm install -g yarn

create-react-app

npx is a fun tool to keep your global npm registry clean for seldom used packages.

npx create-react-app sampleapp
cd sampleapp

Install packages

yarn add node-sass react-bootstrap bootstrap @fortawesome/react-fontawesome \
  @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons \
  react-redux redux holderjs

Changing default files.

We aren’t going to use the following files

rm src/App.css src/index.css src/logo.svg

Lets make a simple bootstrap theme in src/index.scss:

// Override default variables before the import
$body-bg: #ddd;

// Import Bootstrap and its default variables
@import '~bootstrap/scss/bootstrap.scss';

Then edit src/index.js to use that instead. (Including the full file.)

import React from 'react';
import ReactDOM from 'react-dom';
import './index.scss';
import App from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(<App />, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Create src/store.js to initialize the redux store:

import { createStore } from 'redux';
import reducers from './reducers';

export default createStore( reducers );

Create src/reducers.js to boostrap your reducers:

import { combineReducers } from 'redux';

const sample = (state = {}, action) => {
  switch( action.type ) {
    case 'PING':
      return {...state, pong: true }
    default:
      return state;
  }
}
export default combineReducers({
  sample
})

Now we can update src/App.js to tie it all together:

import React, { Component } from 'react';
import { Provider } from 'react-redux';
import store from './store';

class App extends Component {
  render() {
    return (
      <Provider store={store}>
      </Provider>
    );
  }
}

export default App;

At this point you should have a totally empty page that ready to start adding logic.

Adding some Bootstrap

Create a sample navbar in src/components/navbar.js. We are importing only the navbar component of react-bootstrap, only the parts of it that we need. We also have an example of using react-fontawesome to bring in an icon.

import React from 'react';
import Navbar from 'react-bootstrap/Navbar';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faAdjust } from '@fortawesome/free-solid-svg-icons'

const N = () => (
  <Navbar bg='light'>
    <Navbar.Brand href="#home"><FontAwesomeIcon icon={faAdjust} /> Navbar with text</Navbar.Brand>
    <Navbar.Toggle />
    <Navbar.Collapse className="justify-content-end">
      <Navbar.Text>
        Signed in as: <a href="#login">Mark Otto</a>
      </Navbar.Text>
    </Navbar.Collapse>
  </Navbar>
);

export default N;

To see this work, we need to edit src/App.js:

import React, { Component } from 'react';
import { Provider } from 'react-redux';
import store from './store';
import Navbar from './components/navbar';

class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <Navbar/>
      </Provider>
    );
  }
}

export default App;

Lets make some pages

Here’s a simple example of a jumbotron with, with three columns of images with captions below. On mobile they will only show one column.

in src/components/pages/home.js:

import React from 'react';
import Jumbotron from 'react-bootstrap/Jumbotron';
import Container from 'react-bootstrap/Container';
import Button from 'react-bootstrap/Button';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Figure from 'react-bootstrap/Figure';
import 'holderjs';

const Header = () => (
  <Jumbotron fluid>
    <Container>
      <h1 className="display-4">Welcome to the app</h1>
      <p className="lead">
        Templating the boilerplate makes it easier to
        focus on what we are doing!
      </p>
      <hr className="my-4"/>
      <Button variant="primary" size="lg" href="#about">About</Button>
    </Container>
  </Jumbotron>
);

const Feature = ({description}) => (
  <Col md>
    <Figure>
      <Figure.Image width={400} height={300}
        alt="400x300" src="holder.js/400x300" />
      <Figure.Caption className="text-centered">{description}</Figure.Caption>
    </Figure>
  </Col>
)

const Home = () => (
  <>
    <Header/>
    <Container>
      <Row>
        <Feature description="This first"/>
        <Feature description="This second"/>
        <Feature description="This third"/>
      </Row>
    </Container>
  </>
)

export default Home;

To see this, in src/App.js import src/pages/home.js as Home and put the component after the nav bar.

<Provider store={store}>
  <Navbar/>
  <Home/>
</Provider>

Multiple pages

And why not a simple about page at src/pages/about.js, since there’s always going to be some sort of about situation.

import React from 'react';

import Container from 'react-bootstrap/Container';
import Media from 'react-bootstrap/Media';
import 'holderjs';

const About = () => (
  <Container>
    <h1 className="display-4">About this site</h1>
    <p>Here's a history of what has happened</p>
    <hr className="my-5"/>
    <Media>
      <img
        width={64}
        height={64}
        className="mr-3"
        src="holder.js/64x64"
        alt="Generic placeholder"
      />
      <Media.Body>
        <h5>Media Heading</h5>
        <p>
          Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque
          ante sollicitudin commodo. Cras purus odio, vestibulum in vulputate at,
          tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla.
          Donec lacinia congue felis in faucibus.
        </p>
      </Media.Body>
    </Media>
  </Container>
)

export default About;

React routing and actions

We’re going to write out own router, since it’s actually fairly simple and it seems cleaner to keep everything inside of the redux store. We’ll use hash based routing for the time being as an example.

First, lets update our src/reducers.js to include storing the nav state. We’re using window.location.hash to set the initialState of the reducer.

import { combineReducers } from 'redux';

const nav = (state = {hash: window.location.hash}, action) => {
  switch( action.type ) {
    case 'HASH_CHANGED':
      return {...state, hash: action.payload }
    default:
      return state;
  }
}

export default combineReducers({
  nav
})

Lets create a src/actions.js file to actually change the hash when it happens. I’m going to pull in the store directly here so that we have access to dispatch and you can call this method directly from wherever you find yourself.

import store from './store';

export const hashChanged = (hash) => store.dispatch( {type: 'HASH_CHANGED', payload: hash })

Finally, let’s create src/pages/index.js to pull all of this together.

  1. We are creating a Pages component and registering an event listener when the url changes.
  2. If it does, we call the hashChanged action to update the store.
  3. We create a ConnectedRouter component which is passed in the hash from the store to our Router
  4. Right now our Router is just looking for the #about hash specifically, but this is a place to extend in the future.
  5. You can link to these pages using the normal href=“#anchor” style of links. Nothing more magical than that.
import React from 'react';
import { connect } from 'react-redux';
import Home from './home';
import About from './about';
import {hashChanged} from '../actions'

class Pages extends React.Component {
  componentDidMount() {
    // Listen for changes on the window object to update our store state
    this.popListener = window.addEventListener('popstate', () => {
      hashChanged( window.location.hash )
    } )
  }

  render() {
    return <ConnectedRouter/>
  }
}

const Router = ({hash}) => {
  if( hash === "#about" ) {
    return <About/>
  }

  return <Home/>
}

const mapStateToProps = (state) => ({
  hash: state.nav.hash
})

const ConnectedRouter = connect(mapStateToProps)(Router);

export default Pages;

And then we need to change src/App.js to use this new component. Here’s the full version:

import React, { Component } from 'react';
import { Provider } from 'react-redux';
import store from './store';
import Navbar from './components/navbar';
import Pages from './pages';

class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <Navbar/>
        <Pages/>
      </Provider>
    );
  }
}

export default App;

This router is simplistic

We don’t do any parameter parsing, which is something that you could add. Additional this is set to use hash based routing, rather than the arguably more sexy HTML5 ‘pushState’ and ‘replaceState’ routing that would make the url “cleaner”. However I almost always deploy things off of a relative path, so that’s not interesting to me. It may be interesting to you however.

Add "homepage":"." inside of your package.json to force the create-react-app build process to use relative paths.

Note on actions

This is not the normal way that people use actions in a lot of the redux documentation that’s out there. Normally the actions.js file contains action creators which return a Object/hash containing at least an action.type. Then you need to get a hole of a dispatch method on the store object, normally by passing in a second argument into connect (mapDispatchToPros).

This seems unecessarily complicated to me. In our action.js which import the store directly, and when you call one of the exported functions it dispatches to the store directly. I’m not sure what I’m missing but it seems to work well.

Wrap up

I hope this helps!

Read next

See also

Image Manipulation in Firebase

its all javascript

We can manipulate images using JavaScript directly, which can be run both on the server or browser environment. Lets take a look at how we’d do this using create-react-app and firebase. We will deploy a function on firebase that will download the user’s avatar, manipulate the image and overlay it with a mask, and then spit out an image. Project Setup First make sure that you have nvm installed. We’ll need a different version of node for create-react-app then we will for firebase functions.

Read more

Adding Facebook Login with react

you can’t escape it

Sometimes you just can’t get away from facebook. Here’s a quick tutorial on how to add facebook login to your react app. First you need to create a facebook app, which is an involved process especially if you want to let, you know, other people log in to your app. Getting your app approved and otherwise up and running here is left as an excersize for you to figure out.

Read more

Building a hugo site and theme with Bootstrap

hugo is blazing fast

Now that’s its 2018 its time to retool the blog using hugo. Why not? Hugo is built in golang and is blazing fast and everything is cleaner than it was in the middleman years. One of the reasons that I liked middleman – it’s usage of the rails’ Sprockets library – is no longer a strength. The javascript and front-end world has moved over to WebPack and I’ve personally moved over to create-react-app.

Read more