Published on

How to Create a Side Nav for your Tableau Portal with React

Authors

Introduction

The reason we create Tableau Portals is to free ourselves of the constraints imposed on us by the Tableau UI and embark on a journey toward an improved UX. The Side Nav is a UI component many of us have become accustomed to by now and in this post, I’ll walk you through each step needed to make this feature a part of your future portals.

Your finish portal will look like this:

Final Side Navigation

Step-by-step

Publish the dashboards you want listed in your Side Nav

List of dashboards

You can either publish your own set of dashboards or use this workbook I’ve used.

If you are using your own dashboards, be sure each dashboard is set to Automatic Layout

Automatic Layout

And also make sure the dashboards have 0 margin and 0 padding.

No margin or padding

This isn’t required in order for the app to work, but if you’re aiming for a look like the one I’ve created then you’ll want to follow each step exactly.

With those settings in place go ahead and publish the dashboard to your own Tableau Server.

Dashboards being published

If you don’t already have a Tableau Server you can get a free dev server, which is what I’m using. You can conveniently get one of these Development Servers by joining the Data Dev Program.

Create a Git Repo

Unlike previous posts, where GitHub was optional, I insist you use GitHub for this tutorial. There a many benefits, but the primary benefit in this scenario is the image URLs GitHub will create for us.

If you don’t already have git and GitHub set up on your machine here are a few videos you can watch to get everything set up so you can continue following along.

Once you’re all set up, go ahead and create a new repository

New GitHub Repo

With the repo created, you’ll next want to initialize your repo. I’m going to go with the randomly generated name GitHub provides, because that’s always fun and opt to include a .gitignore template for a Node application.

GitHub repo setup

Now we can Clone this repo to our local machine.

Clone the repository

In case you were wondering why my command line looks so odd, I’m using Hyper as my terminal on Mac.

I’m also using VS Code and I’ve set things up so that entering ‘code .’ into my terminal will open VS Code and set the working directory as the active workspace. Here’s a link to an article if you would like to set this up for yourself.

Create and New React App

From the terminal within VS Code run npx create-react-app to create your React starter app in the same directory.

Create React App

You’re ready to go as soon as you see “Happy Hacking!”

React App Created

Now we’ll make our first commit.

git add .

git commit -m ‘Just finished creating the React starter App’

git push origin main

When you refresh GitHub you’ll see a screen like this

Pushed files to GitHub

Clean up the React boilerplate code

I feel like a video walkthrough is more suitable, let’s get into…

After following those steps, your folder should look like the image below.

Removed unnecessary files

After committing those changes your code should like the code in my repository. Throughout the rest of this post, I’ll share significant commit links so you can easily assess the code base at that point in time.

Just to check and make sure everything is working well we can run npm start

If all’s well your default browser will open. If you then look at your browser console you shouldn’t see any errors.

No errors in the console

Build out the folder structure

I still haven’t landed on the best way to structure these apps myself, but for this project, it has worked well enough to add three folders within the ‘src’ directory called: assets, components, and styles

Create empty folders

Create your Data Set

Before we incorporate the Tableau JS API and create a component to render a Viz we’ll want to create our dataset.

We do this first, because the Viz is dependent on the dataset.

In the assets folder create a file called utils.js.

Part of our dataset will be unique ids, so now is also a good time to include the uuidv4 library.

We use this library to make sure each dashboard listed in our side nav has a unique id. React’s diffing algorithm requires these unique IDs to keep track of the state of your application. Without a unique id for each element, React would have a hard time comparing the elements’ State to the application State. Without the ability for React to do this it won’t know when to trigger a rerender.

To install uuidv4 we follow their steps on their website.

Installing uuidv4

Once installed we just need to import that library into our utils.js file and create our dataset.

The data set we’ll be using has the following properties:

  • name
  • author
  • image - The value of the image property will be pulled from the GitHub URL after pushing all of your images to the remote. Once the images are up on GitHub, just click on Raw to see the raw image GitHub is hosting for you.
  • url - These come straight from Tableau Server.
  • id
  • active - The active flag in the data set is used to style that dashboard in the list a bit differently than the other list items. Doing so helps you associate the element that is highlighted with the dashboard at the center of the screen.

If you’d like to copy my dataset you can grab it here. You’ll just need to update the URLs to point to your own Tableau Server.

The image below shows me grabbing a URL by clicking on the share button Tableau Server. You’ll want to do this for each of your dashboards.

Tableau Share Links

Those URLs are the ticket to rendering the right dashboard.

In order to see a thumbnail in the side nav we’ll want to take a screenshot of each dashboard and save those screenshots as PNGs in an image folder within our assets directory.

I use SnagIt to take all of my screenshots, but any screen capture mechanism will do so long as you’re able to save those screenshots as PNGs.

Once those screenshots are all nicely housed within their images folder we can make a commit to GitHub. Here’s a link to the commit taken right after I pushed those images to GitHub.

Now, you can go through and update all of the image URLs in the data source to point to the screenshot images you’ve uploaded or you can use the links in the commit I shared earlier.

If you would like to create your own author names' for the data source you can have some fun with this name generator.

The ‘name’ property can be anything that makes sense for your dashboards.

Obviously, this is a bit of a manual process, but that’s intentional. I chose to provide you with everything you need to create a fun frontend for your portal while saving you from the oh-so-sexy details of setting up the backend to pull these assets down from Tableau Server.

That may have to be another blog post down the road 🤐

Create your Viz component

We’re finally ready to create our first component and add the Tableau JS API.

Let’s start by adding the script tag to our index.html file.

<script src="https://us-east-1.online.tableau.com/javascripts/api/tableau-2.min.js"></script>

Link to the commit taken at this point.

In order to render our Viz we need to create the Viz component, import it into the App and also pass the appropriate data values through to the from the App.

We’ll begin by importing the data and setting the State.

import    from "react"
// Import the data
import data from "./assets/utils"
// Import the styles
import "./App.css"

function App() {
  // State
  const [dashboards, setDashboards] = useState(data())
  const [currentDashboard, setCurrentDashboard] = useState(dashboards[0])

  console.log(currentDashboard)

  return <div className="App"></div>
}

export default App

The code above is going to log out our currentDashboard, which is what we’re going to pass through to the Viz component in the next step.

Now you should see the first object in our data array being logged out. Woo!

Logging our Current Dashboard

With access the values validated, let’s create our Viz component and pass through the currentDashboard.

Viz.js code:

import { useState, useRef, useEffect } from "react"
const    = window

export default function Viz(  ) {
  const [viz, setViz] = useState(null)

  // Set up the arguments to pass into the Tableau Viz function
  const ref = useRef(null)
  const options = {
    hideTabs: true,
    hideToolbar: true,
    width: "900px",
    height: "540px",
  }

  // This function will be run on page load to initialize our viz.
  const initViz = () => {
    if (viz) {
      viz.dispose()
      setViz(null)
    }

    setViz(new tableau.Viz(ref.current, currentDashboard.url, options))
  }

  // Initialize viz when the page loads
  useEffect(initViz, [currentDashboard])

  return (
    <div className="viz-container">
      <h2></h2>
      <h3></h3>
      <div ref=></div>
    </div>
  )
}

The code above assumes that we’ve passed currentDashboard through as a prop.

Let’s circle back to the App component and do that now.

App.js code:

import    from "react"
// Import the data
import data from "./assets/utils"
// Import components
import Viz from "./components/Viz"
// Import the styles
import "./App.css"

function App() {
  // State
  const [dashboards, setDashboards] = useState(data())
  const [currentDashboard, setCurrentDashboard] = useState(dashboards[0])

  console.log(currentDashboard)

  return (
    <div className="App">
      <Viz currentDashboard= />
    </div>
  )
}

export default App

Now we’re able to see the name, author and the viz after we authenticate.

Tableau Authentication

You might have noticed that it’s a bit off-center…

In order to get our Viz in the right spot, we need to add some styles.

Add your first CSS

We’ll start be replacing the App.css code with the following

/* @import-normalize; */
@import './styles/viz.css';

*,
*::before,
*::after {
  margin: 0px;
  padding: 0px;
  box-sizing: border-box;
}

h1,
h2,
h3 {
  color: rgb(54, 54, 54);
}

body {
  font-family: sans-serif;
}

h3,
h4 {
  font-weight: 400;
  color: rgb(100, 100, 100);
}

As soon as you update your CSS you will get an error, because your import is broken.

In order to fix it we need to create our viz.css file

In your src folder create a Viz.css file.

In this new css file enter the following code:

.viz-container {
  min-height: 60vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.viz-container h2 {
  padding: 2rem 0rem 1rem 0rem;
}

.viz-container h3 {
  padding-bottom: 2rem;
  font-size: 1rem;
}

Our commit at this point.

With all of this CSS in place your screen should now look like this:

Centering our Viz

Not bad!

Create your Side Nav component

This is what you’ve been waiting for. The last piece of this React puzzle is creating a Side Nav with a list of all the dashboards in our data set that we’re able to expand and collapse nicely.

Let’s start by bringing in our little hamburger icon so that we have a way to expand and collapse this future list.

We’ll use Font Awesome’s extensive library of icons for this. Just follow their Getting Started scripts here.

Once Font Awesome is installed we can add the bars icon to our App

import    from "react"
// Import the data
import data from "./assets/utils"
// Import components
import Viz from "./components/Viz"
// Import FontAwesome
import    from "@fortawesome/react-fontawesome"
import    from "@fortawesome/free-solid-svg-icons"
// Import the styles
import "./App.css"

function App() {
  // State
  const [dashboards, setDashboards] = useState(data())
  const [currentDashboard, setCurrentDashboard] = useState(dashboards[0])

  return (
    <div className="App">
      <Viz currentDashboard= />
      <FontAwesomeIcon className="bars" icon= />
    </div>
  )
}

export default App

And update our app CSS by adding this code to the bottom of the file:

.bars {
  position: fixed;
  top: 0;
  left: 0;
  margin: 20px;
  cursor: pointer;
}

Our commit at this point.

Now you should see the bars icon positioned nicely in the top-left corner of the screen.

Bar icon

Alright, it’s finally time for the List component.

Go on and add a List component to your components folder and enter the following code:

import React from "react"
// Import FontAwesome
import    from "@fortawesome/react-fontawesome"
import    from "@fortawesome/free-solid-svg-icons"

export default function List({ listStatus, setListStatus }) {
  return (
    <div className={`list ${listStatus ? "active-list" : ""}`}>
      <div className="list-header">
        <h2>Dashboards</h2>
        <FontAwesomeIcon
          className="close"
          icon=
          onClick={() =>

          }
        />
      </div>
    </div>
  )
}

Back in our app, we need to pass through listStatus props and import the List component.

We’ll also be setting the state of our listStatus. The listStatus state is what we’ll use to toggle the side nav in and out.

import    from "react"
// Import the data
import data from "./assets/utils"
// Import components
import List from "./components/List"
import Viz from "./components/Viz"
// Import FontAwesome
import    from "@fortawesome/react-fontawesome"
import    from "@fortawesome/free-solid-svg-icons"
// Import the styles
import "./App.css"

function App() {
  // State
  const [dashboards, setDashboards] = useState(data())
  const [currentDashboard, setCurrentDashboard] = useState(dashboards[0])
  const [listStatus, setListStatus] = useState(false)

  return (
    <div className="App">
      <List listStatus= setListStatus= />
      <Viz currentDashboard= />
      <FontAwesomeIcon className="bars" icon= />
    </div>
  )
}

export default App

Now you’re going to see a mess, but all of our components are being rendered.

Unformatted list

Our commit at this point.

Let’s add some styles so that our list starts looking the part.

Create a new List.css file in your styles directory and add the following code:

.list {
  position: fixed;
  top: 0;
  left: 0;
  width: 20rem;
  height: 100%;
  background-color: white;
  box-shadow: 2px 2px 50px #b3b3b3ff;
  overflow: scroll;
  transition: all 0.5s ease;
  /* This will bring the list to the top */
  z-index: 9999;
}

.list .close {
  cursor: pointer;
}

.list-header {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
}

*::-webkit-scrollbar {
  width: 5px;
}

*::-webkit-scrollbar-track {
  background: transparent;
}

*::-webkit-scrollbar-thumb {
  background-color: rgba(155, 155, 155, 0.5);
  border-radius: 20px;
  border: transparent;
}

Then in the App.css file update add another import statement

@import "./styles/List.css";

Now you’ll see something that’s starting to look like that side nav!

Formatted side nav

Our commit at this point.

The next step is to fill in our side nav with the dashboards and allow the user to change them by making a selection.

The first step is passing the dashboards through to our List component

<List
  listStatus={listStatus}
  setListStatus={setListStatus}
  dashboards={dashboards}
  setDashboards={setDashboards}
  setCurrentDashboard={setCurrentDashboard}
/>

This list is comprised of a list header section and a row for each object from data object.

Earlier I pointed out each property of the individual objects. Now we’re going to map over that array (dashboard) and create a ListItem in our List for each object in our array.

Feel free to add or remove objects to the array to adjust the length of the list.

import React from "react"
// Import FontAwesome
import    from "@fortawesome/react-fontawesome"
import    from "@fortawesome/free-solid-svg-icons"
// Import components
import ListItem from "./ListItem"

export default function List({
  listStatus,
  setListStatus,
  dashboards,
  setDashboards,
  setCurrentDashboard,
}) {
  return (
    <div className={`list ${listStatus ? "active-list" : ""}`}>
      <div className="list-header">
        <h2>Dashboards</h2>
        <FontAwesomeIcon
          className="close"
          icon=
          onClick={() =>

          }
        />
      </div>
      <div className="list-items">
        {dashboards.map((dashboard) => (
          <ListItem
            dashboard=
            dashboards=
            setDashboards=
            setCurrentDashboard=
            // React requires the key prop with a unique values on components
            // that have the same props.
            key=
            id=
          />
        ))}
      </div>
    </div>
  )
}

For each of the dashboards in the list we attach a click handler.

This handler is going to check to see whether that dashboard is active or not. If it isn’t active the active property will be set to true.

The updating of the current dashboard and the dashboards State will trigger a rerender and the dashboard in the list with the active value of true will get the class of selected which causes our CSS to kick in so we can more easily determine which dashboard is active.

Now create a ListItem component with the following code:

export default function ListItem({
  dashboard,
  dashboards,
  setDashboards,
  setCurrentDashboard,
  id,
}) {
  const dashboardSelectHandler = () => {
    setCurrentDashboard(dashboard)
    // Add active state
    const newDashboards = dashboards.map((dashboard) => {
      if (dashboard.id === id) {
        return {
          ...dashboard,
          active: true,
        }
      } else {
        return {
          ...dashboard,
          active: false,
        }
      }
    })

    setDashboards(newDashboards)
  }

  return (
    <div
      className={`list-item ${dashboard.active ? "selected" : ""}`}
      onClick=
    >
      <img src= alt=></img>
      <div className="dashboard-description">
        <h3></h3>
        <h4></h4>
      </div>
    </div>
  )
}

Now you can see your list items and make selections. Updating the current dashboard will also cause our Viz to rerender with the dashboard that was selected. The dashboard… just look a little off

Selecting unformatted items from the list

Our commit at this point.

Let’s go update our List style now

.list {
  position: fixed;
  top: 0;
  left: 0;
  width: 20rem;
  height: 100%;
  background-color: white;
  box-shadow: 2px 2px 50px #b3b3b3ff;
  overflow: scroll;
  transition: all 0.5s ease;
  /* This will bring the list to the top */
  z-index: 9999;
}

.list .close {
  cursor: pointer;
}

.list-header {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
}

.list-item {
  display: flex;
  align-items: top;
  padding: 0.75rem;
  cursor: pointer;
}

.list-item:hover {
  background-color: #fafafaff;
}

.list-item img {
  width: 35%;
}

.dashboard-description {
  padding-left: 1rem;
  text-transform: uppercase;
}

.dashboard-description h3 {
  font-size: 0.8rem;
  font-weight: bold;
  margin-bottom: 4px;
}

.dashboard-description h4 {
  font-size: 0.7rem;
}

.selected {
  background-color: #eeef;
}

*::-webkit-scrollbar {
  width: 5px;
}

*::-webkit-scrollbar-track {
  background: transparent;
}

*::-webkit-scrollbar-thumb {
  background-color: rgba(155, 155, 155, 0.5);
  border-radius: 20px;
  border: transparent;
}

Looking good!

Selecting formatted list items

Our commit at this point.

At this point, we’re able to select dashboards from our list and update the Viz rendered in the middle of the screen.

Hook up the controls

Now we want to make it so that we can show and hide our side nav.

We’ll first update our List CSS with the following code

.list {
  position: fixed;
  top: 0;
  left: 0;
  width: 20rem;
  height: 100%;
  background-color: white;
  box-shadow: 2px 2px 50px #b3b3b3ff;
  overflow: scroll;
  transform: translateX(-100%);
  transition: all 0.5s ease;
  /* This will bring the list to the top */
  z-index: 9999;
}

.list .close {
  cursor: pointer;
}

.list-header {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
}

.list-item {
  display: flex;
  align-items: top;
  padding: 0.75rem;
  cursor: pointer;
}

.list-item:hover {
  background-color: #fafafaff;
}

.list-item img {
  width: 35%;
}

.dashboard-description {
  padding-left: 1rem;
  text-transform: uppercase;
}

.dashboard-description h3 {
  font-size: 0.8rem;
  font-weight: bold;
  margin-bottom: 4px;
}

.dashboard-description h4 {
  font-size: 0.7rem;
}

.selected {
  background-color: #eeef;
}

.active-list {
  transform: translateX(0%);
  opacity: 1;
}

*::-webkit-scrollbar {
  width: 5px;
}

*::-webkit-scrollbar-track {
  background: transparent;
}

*::-webkit-scrollbar-thumb {
  background-color: rgba(155, 155, 155, 0.5);
  border-radius: 20px;
  border: transparent;
}

And add an event listener to the hamburger icon in our App component

import    from "react"
// Import the data
import data from "./assets/utils"
// Import components
import List from "./components/List"
import Viz from "./components/Viz"
// Import FontAwesome
import    from "@fortawesome/react-fontawesome"
import    from "@fortawesome/free-solid-svg-icons"
// Import the styles
import "./App.css"

function App() {
  // State
  const [dashboards, setDashboards] = useState(data())
  const [currentDashboard, setCurrentDashboard] = useState(dashboards[0])
  const [listStatus, setListStatus] = useState(false)

  return (
    <div className="App">
      <List
        listStatus=
        setListStatus=
        dashboards=
        setDashboards=
        setCurrentDashboard=
      />
      <Viz currentDashboard= />
      <FontAwesomeIcon
        className="bars"
        icon=
        onClick={() =>

        }
      />
    </div>
  )
}

export default App

With those two updates, we can now open and close the List. The onClick event we add toggles that side nav in by toggling the active-list class on and off and our CSS style positions the List based on that class.

Toggle the side nav and select items

Our commit at this point.

Conclusion

Way to go!

If you’ve followed along up to this point, you now have a nice example to add to your portfolio, an example to use in your own projects, or, at the very least, something to tinker with and extend further.

If you were to extend it, the best thing I can think to do would be to pull the data source directly from Tableau Server instead of using the static data source we’re using in this example.

Thanks for reading!