Blog   Home

Practicing Tech Design with React, Storybook, and Nivo

Design · Learning · Evaluating

I spent the weekend making a technical design and implementing it to challenge myself and evaluate a few different unfamiliar libraries. I’ve read a lot of positive reviews of Storybook, so I figured creating a small project would be a good way to evaluate. Driven by my interest in Nivo, a charting library, I thought an easy bit of data to visualize would be my TreeHouse profile points.

The React Suspense API and react-fetching-library were technologies that have piqued my interest lately, so I included these intentionally. I’m rather happy with the result other than having to create my own node server to get around a Treehouse server issue (or rather an issue with the client library. More on that later).

I first created a technical design to plan out the broad, architectural pieces needed for the app. The process involved researching what would be needed from the newer and unfamiliar technologies and formulating a procedure for implementing them.

Then, I executed my plan and took note of my opinions regarding the unfamiliar technologies and the hiccups along the way.

The result

Self-Chart Technical Design

High Level Design

Chosen Technologies

High level Todo items

No state management library is needed for this simple example. Though no other library is really needed either for something this simple. I just want to learn about them.

Low Level Design

Setup

  1. Use create-react-app with typescript to get up and running.
  2. Start a git repo.
  3. Deploy on codesandbox to show the world.

Storybook

Dependencies:

    yarn add
    yarn add -D typescript
    yarn add -D awesome-typescript-loader
    yarn add -D @types/storybook__react
  1. Create a .storybook directory and add config.js file
  2. Create storybook script in your build system
    • In our case, this is npm.
      • In package.json write script "storybook": "start-storybook -p 6006 -c .storybook
  3. Configure the storybook config file with the following script:
      import { configure } from "@storybook/react"
    
      // This will iterate through src
      // looking for any extension that matches stories.ts
    
      const req = require.context("../src", true, /stories.ts$/);
    
      function loadStories() {
        req.keys().forEach(file => req(file));
      }
    
      configure(loadStories, module);
    
  4. For Storybook to work with TypeScript, create a custom webpack config inside at

    .storybook/webpack.config.js

      module.exports = ({ config }) => {
        config.module.rules.push({
          test: /\.(ts|tsx)$/,
          use: [
            {
              loader: require.resolve('awesome-typescript-loader'),
            }
          ],
        });
        config.resolve.extensions.push('.ts', '.tsx');
        return config;
      };
    
  5. Create tsconfig

    .storybook/tsconfig.json

     {
       "compilerOptions": {
         "outDir": "build/lib",
         "module": "commonjs",
         "target": "es5",
         "lib": ["es5", "es6", "es7", "es2017", "dom"],
         "sourceMap": true,
         "allowJs": false,
         "jsx": "react",
         "moduleResolution": "node",
         "rootDirs": ["src", "stories"],
         "baseUrl": "src",
         "forceConsistentCasingInFileNames": true,
         "noImplicitReturns": true,
         "noImplicitThis": true,
         "noImplicitAny": true,
         "strictNullChecks": true,
         "suppressImplicitAnyIndexErrors": true,
         "noUnusedLocals": true,
         "declaration": true,
         "allowSyntheticDefaultImports": true,
         "experimentalDecorators": true,
         "emitDecoratorMetadata": true
       },
       "include": ["src/**/*"],
       "exclude": ["node_modules", "build", "scripts"]
     }
    

PieChart Component

  1. Create a chart component under src/components named PieChart.ts
      import * as React from "react"
    
      const PieChart = () => {
      	return ...
      }
    
  2. Import the pie chart component from @nive/pie

      import { ResponsivePie } from '@nivo/pie'
    
  3. Use the component within the PieChart component we created
  4. Create a chart component under src/components named PieChart.tsx

      import * as React from "react"
      import { ResponsivePie } from '@nivo/pie'
    
      const PieChart = () => {
      	return (
      		<ResponsivePie
      			...
      		/>
      	)
      }
    
  5. Define data to be used in pie chart

      import * as React from "react"
      import { ResponsivePie } from '@nivo/pie'
    
      /*
      	Required props are: { data }
    
      	Parent element is required to have width and height
      	when using responsive chart components
    
      	data type is Array<{
             id:    string | number,
             value: number
      	}>
      */
    
      const PieChart = (data) => {
      	return (
      		<ResponsivePie
      			...
      			data={data}
      		/>
      	)
      }
    

Storybook Component Setup

  1. Create an pie_chart.stories.tsx file next to the PieChart.tsx component file

      import * as React from "react"
    
      import { storiesOf } from "@storybook/react"
      import { PieChart } from "./PieChart"
    
      /*
      	Stories are to demonstrate the capabilities of a component
      	In this instance, we will need to supply data to the pie chart
      */
    
      storiesOf('PieChart', module)
      	.add("Demonstrate simple data passed to chart", () => {
      		<PieChart data= />
      })
    

To run storybook : npm run storybook

Client

Dependencies:

yarn add react-fetching-library
  1. Create the directory src/client
  2. Create the file api.ts

    and setup to use library:

      import { createClient } from "react-fetching-library"
    
      // Often the host endpoint would be hidden in evironment variables
      const HOST = 'HOST ENDPOINT HERE'
    
      export const requestHostInterceptor = host => client => async action => {
          return {
            ...action,
            endpoint: `${host}${action.endpoint}`,
          };
        };
    
    
      export const Client = createClient({
          requestInterceptors: [requestHostInterceptor(HOST)]
      })
    
  3. Create the query action in src/client/actions/fetchData.ts

      export const fetchData = {
        method: 'GET',
        endpoint: '/',
      };
    
  4. Create the client provider component and suspense wrapper

    src/index.ts

      	...
      import { ClientContextProvider } from 'react-fetching-library';
      import { Client } from './api/Client';
    
      const App = () => {
        return (
          <ClientContextProvider client={Client}>
      			<Suspense fallback={<ProgressSpinner />}>
      				{// Whatever children need the client>}
      			</Suspense>
          </ClientContextProvider>
        );
      };
      ...
    
  5. Create container component for chart component to handle the query for data

    src/components/chart_container.tsx

      ...
      import { useQuery } from 'react-fetching-library';
      import { fetchData } from '../client/actions'
    
      export const ChartContainer = () => {
      	const { loading, payload, error, query } = useQuery(fetchData)
    
      	if (error) return <ErrorButton onClick={query}/>
      	// Will add some data tranformation step here
    
      	return <PieChart data={transformedData} />
      }
    

Data Tranformation Utility

  1. Create the directory src/util
  2. Inside that directory, create the file fetchData_util.ts
  3. Create a function in the file that takes json data of this shape:

      {
      	"name": "Beavis"
      	"points": {
      		"total": 12345,
      		"javascript": 2345,
      		"ruby": 0,
      		...
      	}
      }
    

    and produces data of this shape:

      [
        {
          "id": "javascript",
          "label": "javascript",
          "value": 2345,
        },
      	...
      ]
    

    Removing the “total” and any data with the value of 0

  4. Use this utility function withing the component that fetches the data

      import { useQuery } from 'react-fetching-library';
      import { fetchData } from '../client/actions'
    
      export const ChartContainer = () => {
      	const { loading, payload, error, query } = useQuery(fetchData)
    
      	if (error) return <ErrorButton onClick={query}/>
    
      	const transformedData = transformData(payload)
    
      	return <PieChart data={transformedData} />
      }
    
  5. Create the directory tests
  6. Create the file tests/fetchData_test.js
  7. Write some tests considering all possibilities for the utility function

      describe("Confirm transformData", () => {
      	test("Should remove key of total from remote data")
      	test("Should remove objects with value of 0")
      })
    

Spinner

  1. Create the spinner component under the components directory
  2. Should be a simple emoji animated to spin with css animations
  3. Use as the suspense fallback

Retrospective

There were a few surprises when implementing this. Some were in Storybook while getting it set up. After following the documentation rather than a tutorial, I got it to work properly.

The other more frustrating surprise was that the client library I used makes an OPTIONS request with every GET request and the Treehouse server was not playing along. I would receive a 500 error on the OPTIONS request, then never get to a successful GET request. By using Postman, I could confirm a GET would work fine without the OPTIONS request. The client library didn’t have a way to configure the fetch to drop the OPTIONS request so I ended up creating a node server to be a proxy to the data. The node server made the same GET request, then passed it along to my app. 3 hours later, problem solved.

Conclusions

StoryBook

Storybook seems like an awesome tool to help guide the design of components. I see a cost in learning how to use it and the time spent in designing a component. One could easily burn hours designing a single component. I enjoy Storybook’s workflow of isolating a component and its influence on making decisions of a components API. I look forward to learning more about this tool.

React-fetching-library

This library is newer (2 months old as of writing this!) but I have really good feelings about it already. The docs are well written and very helpful. The composability is great from my perspective. After they work out a few kinks, it could be a great tool for componentized fetch requests.

Nivo

I chose Nivo for charting mostly because their site is pretty. The documentation is really great as well. This being the first time using it, I thought it was rather easy to get started and figure out how to plug it into my project. A lot of the styling of charts is handled for you, and some may consider that a plus or a minus, but most of it is customizable. It’s no D3 ;) but being the react nerd that I am, I found it to be quite an easy and visually pleasing way to jumpstart charts in my project. I look forward to using this library in the future.

Tech designs

As mentioned, part of this weekend activity was to practice technical designs. Our principal engineer wrote a post on the importance of technical design that goes more in-depth on its value. After using this approach for my weekend hacking extravaganza, I saw how it definitely kept my head out of the details of the code and focused on the bigger picture. After getting together a “template” to follow, implementing the code went rather quickly, then much of the time was spent debugging smaller problems.

This project was admittedly an overuse of libraries and tools, but I now have a functioning template to create charts using React, Nivo, and Storybook.

TL;DR

Repos can be found here:

https://github.com/jonwalz/treehouse_chart

https://github.com/jonwalz/treehouse_profile_server

We are the creators of Shoutbase, a tool to optimize how software developers track and spend their time.

Want to know when we post new articles?