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
- React
- TypeScript
- React-fetching-library - newer fetch client for react
- Jest
- Nivo (Charting library)
- Storybook
- Postman
High level Todo items
- Bootstrap a development cycle by creating a storybook version of components needed in this project.
- Create UI layer with React.
- Define tests, components, client, and data transformation.
- Create client for fetching needed data.
- Create data transformation utility for UI consumption.
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
- Use create-react-app with typescript to get up and running.
- Start a git repo.
- 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
-
Create a
.storybook
directory and addconfig.js
file -
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
- In
- In our case, this is npm.
-
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)
-
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 }
-
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
-
Create a chart component under
src/components
namedPieChart.ts
import * as React from "react" const PieChart = () => { return ... }
-
Import the pie chart component from
@nive/pie
import { ResponsivePie } from "@nivo/pie"
-
Use the component within the PieChart component we created
-
Create a chart component under
src/components
namedPieChart.tsx
import * as React from "react" import { ResponsivePie } from '@nivo/pie' const PieChart = () => { return ( <ResponsivePie ... /> ) }
-
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
-
Create an
pie_chart.stories.tsx
file next to thePieChart.tsx
component fileimport * 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={{ exampleData }} /> }, )
To run storybook :
npm run storybook
Client
Dependencies:
yarn add react-fetching-library
-
Create the directory
src/client
-
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)], })
-
Create the query action in
src/client/actions/fetchData.ts
export const fetchData = {
method: "GET",
endpoint: "/",
}
-
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> ); }; ...
-
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 transformation step here return <PieChart data={transformedData} /> }
Data transformation Utility
- The incoming data from Treehouse is not the correct shape and we need to transform it into a usable shape for the UI
-
Create the directory
src/util
-
Inside that directory, create the file
fetchData_util.ts
-
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
-
Use this utility function within 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} /> }
-
Create the directory
tests
-
Create the file
tests/fetchData_test.js
-
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
- Create the spinner component under the
components
directory - Should be a simple emoji animated to spin with css animations
- 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
- Storybook seems great for large projects that have many reused components.
- Nivo makes very pretty charts. Great documentation. Understandable API.
- React-fetching-library has great composability. It is young but has great potential.
- Creating a technical design greatly increases the speed of development and reveals the difficult parts sooner.
Repos can be found here: