DEV Community

Elias Baez
Elias Baez

Posted on

Setting up a dynamic single-page React & Bulma app using Sass

Developing & Deploying a Full Stack MERN App to a VPS (DreamHost)

The first step is to create the directory.

mkdir website-example
cd website-example

The second step would be to create the two separate folders. One will hold the server files, the Node.js backend (and corresponding MongoDB Atlas connect), and the second directory will contain the React client. Start with the first.

mkdir api

And for the second, we will be using create-react-app, because going through npm run build seems to me to be the easiest way to translate React files into a VPS file-hosting environment.

npx create-react-app client

Planning out how I am going to build out this app, the first thing I want to do is actually clean up all the parts of the create-react-app default environmental files and stuff that I don’t myself use.

cd client
code .

Remove the following:

App.test.js
reportWebVitals.js
setupTests.js
logo.svg

As well as references to these files in the index.js and App.js files. Also, gut the content within the

tags of the of the App.js file entirely. You’re going to end up swapping all of this out anyway. My App.js file now only has:

Elias Baez

between the div tags. At this point, try running the app using npm start and it will load to your localhost:3000 port, and should show whatever you’ve replaced your App.js

content with.

It should look like absolutely nothing special. At this point, we are also going to install Bulma, a CSS library that works well with React for mobile development and, as far as I can tell, doesn’t translate too badly from deployment to production. In addition, Bulma has a great set of customizable Sass variables, so we will install Sass here too. Because we’ve used create-react-app, we’re going to have to install an earlier version of node-sass that goes better with our React environment. Within the ‘client’ directory, run:

npm install –save bulma node-sass@4.14.1

After this, you’ll want to rename all of your css files to have “.scss” file types, or else the app will break all over.

Now, instantiate the directory that are going to organize your ‘src’ files.

cd src
mkdir styles components axios

The first will hold your sass files, the second will hold your React components, and the third will be where API calls from the client are processed and directed. The main API, which actually contacts my database, will be located in the Node.js files. This axios directory will be where we call out to the server from.

Before we make our first component, there is just a little more to finish setting up Bulma. First, move the existing “App.scss” file to the “Styles” folder. This will be the main entry point for our app styling. Next, create a file named ‘variables.scss’ within the same directory. This will be where we use Sass to customize our use of the Bulma framework. Make sure to update the App.js file to search for its styling in the new subdirectory, too.

mv ./App.scss ./styles
touch styles/variables.scss

Finally, delete all of the contents of the App.scss page and add the following to the top of the file, in this particular order:

@charset "utf-8";
@import './variables.scss';
@import '../../node_modules/bulma/bulma.sass';

You’ll see how useful this will be later.

Now, we are ready to start building out the client. The goal is to have a single-page application that is able to populate a structured set of fields depending on which of two paths the user asked to be taken down. We are going to store that data, and act on it, using the built-in React Hooks feature.

useState tells the program what variables to watch for. useEffect will tell our program to run a function either on page load or whenever a relevant variable is altered.

At the top of your App.js file, adjust it to say:

import React, { useEffect, useState } from 'react';
import './styles/App.scss';

function App() {

const [isHome, setIsHome] = useState(true);
const [path, setPath] = useState('home');

useEffect(() => {
console.log('use effect hook ran');
}, [])

This set-up will print out a console.log when the page loads. There should be two ‘state’ variables set in the browser: isHome, and path. IsHome and path are variables that can be accessed within the rest of the file. setIsHome and setPath are functions that are used to change the value of those state variables. In React, you cannot act on state yourself to change it manually.

Our useEffect hook is right now a simple arrow function. The [] at the end of the function declaration is where the ‘observed variable’ of the hook will be. Were we to place ‘path’ or ‘isHome’ within that array, then the useEffect hook’s code would run whenever those variables were changed. This creates the dynamism. At present, with it empty, the hook’s code will only run once, on page load.

To see one of these variables in use, I set my return JSX to say:

<div className="App">
  <p>Elias Baez</p>
  <p>you are {path}</p>
</div>

With {path} pulling and displaying that variable from state.

Take a breather. Next, we are going to incorporate our first Bulma component [a hero banner] and some slight styling because, although it’s not “minimum viable product,” I think it helps me keep going if it looks at least shaped up a little. This will also help us see the Hook’s logic in use, as we don’t have any data to populate the fields that will change yet.

I’ve started the process of the banner within the App.js file, though we are going to move it to its own component shortly. Bulma works by CSS classes, so it’s very easy to incorporate into this environment. Just be careful that, if you’re following Bulma’s documentation, you be sure to replace every instance of “class=” with “className=” or else it will not be recognized.

The return statement now looks like this:

  `<div className="App">
  <section className="hero is-primary is-fullheight-with-navbar">
    <div className="hero-body">
      <h1 className="title">
        Elias Baez
      </h1>
      <h2 className="subtitle">
        Programmer, Poet, Editor
      </h2>
    </div>
  </section>
</div>`

Next, we’ll get into why I like Bulma and Sass so much. The ‘hero banner’ component borrowed from Bulma’s component library has a default color, using the $primary color variable. We can directly effect those attributes using Sass. In the variables.scss file, change the $primary value by adding:

$primary: #4a7a96

And the color should change.
The Bulma documentation tells us that the hero banner, to work in full height, needs a ‘hero head’ and a ‘hero footer’ as well. This is where we will add our Navbar component and our Footer component, respectively. So going back to Bulma, I will add those two components now. With all of this logjammed into one App.js file, this is what things look like:

return (
    <div className="App">
      <section className="hero is-primary is-fullheight">
        <div className="hero-head">
          <nav className="navbar is-fixed-top" role="navigation" aria-label="main navigation">
            <div className="navbar-brand">
              <a className="navbar-item" href="https://baez.us">
                Elias Baez
              </a>
              <a role="button" className="navbar-burger" aria-label="menu" aria-expanded="false">
                <span aria-hidden="true"></span>
                <span aria-hidden="true"></span>
                <span aria-hidden="true"></span>
              </a>
            </div>
            <div className="navbar-menu">
              <div className="navbar-end">
                <a className="navbar-item" href="https://baez.us">
                  Programming
                </a>
                <a className="navbar-item" href="https://baez.us">
                  Poetry
                </a>
              </div>
            </div>
          </nav>
        </div>
        <div className="hero-body">
          <h1 className="title">
            Elias Baez
          </h1>
          <h2 className="subtitle">
            Programmer, Poet, Editor
          </h2>
        </div>

        <div className="hero-foot">
          <nav className="tabs is-boxed is-fullwidth">
            <div className="container">
              <ul>
                <li className="is-active">
                  <a>Overview</a>
                </li>
                <li>
                  <a>Contact</a>
                </li>
                <li>
                  <a>About</a>
                </li>
                <li>
                  <a>GitHub</a>
                </li>
                <li>
                  <a>LinkedIn</a>
                </li>
              </ul>
            </div>
          </nav>
        </div>
      </section>
    </div>
  );

Running this, the page looks decent, but there’s a lot left to be done. The first thing to tackle, however, is how bloated our App.js is becoming. It’s time to start to modularize the app and begin to chunk pieces off into components. This will also make it easier to adjust the moving parts that, you’ll see, are all about to come into play. We will be using functional components rather than Class-based components.

cd client
touch src/components/Navbar.js src/components/Footer.js

This will create the two files we will use as components. For now, the “hero-body” will remain in the App.js file, because we are going to build some stuff around it still. The other two components will be relatively static. The Footer component will look like this, to start:

import React from ‘react’;

const Footer = () => {

<div className="hero-foot">
      <nav className="tabs is-boxed is-fullwidth">
        <div className="container">
          <ul>
            <li className="">
              <a>Overview</a>
            </li>
            <li>
              <a>Contact</a>
            </li>
            <li>
              <a>About</a>
            </li>
            <li>
              <a>GitHub</a>
            </li>
            <li>
              <a>LinkedIn</a>
            </li>
          </ul>
        </div>
      </nav>
    </div>
)

}
export default Footer;

And then in the App.js file, at the top, import the component. The below is how it is added to JSX.

import Footer from ‘./components/Footer’;

//and then within the App return call, will pull and render the entire component. The code should look different, but the view will remain the same:

    <div className="hero-body">
      <h1 className="title">
        Elias Baez
      </h1>
      <h2 className="subtitle">
        Programmer, Poet, Editor
      </h2>
    </div>
    <Footer/> //inserted component
  </section>

Do the same with the Navbar, and this will be the new App return:

<div className="App">
  <section className="hero is-primary is-fullheight">
    <Navbar/>
    <div className="hero-body">
      <h1 className="title">
        Elias Baez
      </h1>
      <h2 className="subtitle">
        Programmer, Poet, Editor
      </h2>
    </div>
    <Footer/>
  </section>
</div>

A lot easier to manage! As a note: If this were a multi-page app, and we were using a React Router, then all of the tags would have to become tags. But because we are creating a single page app and don’t need to use the router for that, the tags should work fine here.

Now that we have these components, we will add some Sass and some buttons in order to get the React Hooks broken in, before putting them to higher levels of work.

Back in the variables.scss file, we’re going to change a couple more Bulma defaults. I am using a five-tone pallette I found here. Add this to the file:

$primary: #4a7a96
$info: #333f58
$dark: #292831
$success: #fbbbad
$link: #ee8695

To show how the React Hook can work to toggle state in the app, add the following:

const [path, setPath] = useState('home');
const [color, setColor] = useState('hero is-primary is-fullheight');

and within the return statement:

This is a very rough implementation of what we will be doing, but this should begin to show you the picture. To quickly supe the background up, I have added a gradient (which is basic, according to some people) using this tool. This I added in the App.scss file as so:

.hero-body {
background: #4A7B96;
background: -webkit-linear-gradient(left, #4A7B96, #EE8695);
background: -moz-linear-gradient(left, #4A7B96, #EE8695);
background: linear-gradient(to right, #4A7B96, #EE8695);
}

I also made the hero-head ‘background: transparent’ so the navbar doesn’t jar against the gradient. Finally, I hid the footer because its ‘tab’ construction was being irky and also jarring against the gradient. It may either stay hidden (I don’t love footers) or I’ll reintroduce it later.

In any case, now that we have some styling incorporated, I want to begin to program the possibility of going down separate paths. We’ll start by creating another button, and changing the event callback on what we have.

        <h1 className="title">
          Elias Baez
        </h1>
        <h2 className="subtitle">
          {path}
        </h2>
        <button className="button is-large" onClick={() => {setPath('coding')}}>
          coding
        </button>
        <button className="button is-large" onClick={() => {setPath('poetry')}}>
          poetry
        </button>

With these changes, the buttons will immediately effect the render of the page.

Next, we’re going to add in the columns that are the main body of the app. I’m going to use Bulma’s ‘nested columns’ component to do so.

<div className="App">
  <section className={color}>
    <Navbar/>
    <div className="hero-body">
        <div className="container has-text-centered">
          <div className="columns">
            <div className="column is-full">
              <h1 className="title">Elias Baez</h1>
              <h2 className="subtitle">{path}</h2>
              <div className="columns is-mobile">
                <div className="column">
                  <button 
                  className="button is-large" 
                  onClick={() => {setPath('coding')}}>
                    coding
                  </button>
                </div>
                <div className="column">
                  <button className="button is-large" onClick={() => {setPath('poetry')}}>
                    poetry
                  </button>
                </div>
              </div>
            </div>
          </div>
      </div>
    </div>
    <Footer/>
  </section>
</div>

Our App is getting pretty unwieldly, so soon we’ll be divvying modules out into components. But first, at present, this should allow you to have a fairly decent looking page with two buttons that responsively change the center text. Now, to supe things up, we’re going to create the two data-displaying components that our established columns will cycle between.

cd client
touch src/components/LeftActive.js src/components/LeftSupport.js
touch src/components/RightActive.js src/components/RightSupport.js

By combining React Hooks with props passed into these column options, the single page will dynamically adjust to whatever has been effected by the user. This is where the ‘isHome’ React Hook comes in. When the user arrives at the website, const [isHome, setIsHome] = useState(true) means that isHome will be true until something sets it as false. At present, when a button is clicked, it will now both set the ‘path’ variable as well as negate the ‘isHome’ variable. This, paired with JSX {} boolean logic makes it work pretty easily:

                <div className="column">
                  {isHome &&
                  <button 
                  className="button is-large" 
                  onClick={() => {setPath('coding'); 
                  setIsHome(false)}}>coding</button>}

And now, when a button is clicked, both buttons disappear but the site information updates, clearing space for me to render the next elements.

Adding in the responsive components depending on the path is only a matter of expanding the Boolean JSX logic a little bit:

        {isHome &&
                  <button 
                  className="button is-large" 
                  onClick={() => {setPath('coding'); 
                  setIsHome(false)}}>coding</button>}

                  {!isHome &&
                  path === 'coding' &&
                  <LeftActive/>}

                </div>
                <div className="column">

                  {isHome && 
                  <button className="button is-large" 
                  onClick={() => {setPath('poetry'); 
                  setIsHome(false)}}>
                    poetry
                  </button>}

                  {!isHome && 
                  path === 'coding' && 
                  <RightSupport/>}

Perform this invertedly as well (with LeftSupport and RightActive).

With that all roughly established, we are going to transition to setting up our Node.js server. And that’s a story for another day.

Top comments (0)