/ React

React, Storybook & TypeScript

I've been looking for some new stuff to learn lately, and instead of trying to learn one new thing, I decided to try three :)

The source for my example repo is up on GitHub.

React

I settled on trying to get React and Storybook working with TypeScript.

Most people in programming circles have at least a cursory awareness of React at this point - it's the in vogue JavaScript library for building frontend and mobile applications.

Storybook

Storybook is gaining traction but not quite as well known; it's a really interesting interactive environment for creating and displaying UI components written with React.

Their GIF sums it up pretty well:
Storybook gif

TypeScript

TypeScript is more focused on the development process, introducing types and structures like interfaces as an extension to JavaScript to make development more predictable and safer.

Creating-React-App

As it turns out, not that many people have tried to use TypeScript with Storybook, and their examples were sometimes out of date using old versions of Storybook and some parts weren't working exactly as expected.

Plus I wanted to learn for myself, so I started out with the TypeScript version of create-react-app which is the recommended way to start a React app.

> npm i -g create-react-app
> create-react-app storybook3-ts-example --scripts-version=react-scripts-ts

This gets me a fresh React app setup with TypeScript installed and mostly configured.

The Story(book) begins

Next step was to try setting up Storybook:

> npm i -g @storybook/cli
> cd storybook3-ts-example
> getstorybook

Thankfully even though we have a TypeScript-ified React app, Storybook will set itself up as per normal, and we end up with a configured instance, and some sample stories (written in JS currently).

I was able to run it with

> npm run storybook

And play around with the examples as shown in the GIF above.

Now it was time to try rewriting stories into TypeScript.

The tough stuff begins

My first idea was just to rename the example stories to .tsx files, which are TypeScript files with JSX included. Of course, it wasn't as easy as that, as I got an instant error:

Module build failed: Error: ENOENT: no such file or directory, open 'C:\Users\Josh\storybook3-ts-example\stories\index.js'

So Storybook wasn't loading my stories as they are TS files.

I changed up .storybook/config.js to read all .tsx files like so:

import { configure } from '@storybook/react';

const req = require.context('../stories', true, /\.tsx?$/)

function loadStories() {
  req.keys().forEach((filename) => req(filename))
}

configure(loadStories, module);

Which got me as far as a new error (a bright red one, in fact):
Bright red error

It looks like Storybook isn't using the normal Webpack process that create-react-app setup for us. If we check the docs, it turns out they do give us a way to extend the Webpack config, by adding a webpack.config.js to the .storybook directory, so we can setup a TypeScript loader to process the story files.

I went with this to begin with, which takes .tsx files and runs them through babel-loader and ts-loader:

const path = require("path");
const include = path.resolve(__dirname, '../');

module.exports = {
  module: {
      rules: [
          {
            test: /\.tsx/,
            loader: 'babel-loader!ts-loader',
            exclude: /node_modules/,
            include
          }
      ]
  }
};

This required me to re-run Storybook, but after that I was greeted with... more errors :(

2017-08-12-16_43_42-Storybook

At least these weren't quite so red. And looking at them, it seems quite positive, we just need to convert our TypeScript files fully rather than just renaming them from .jsx to .tsx.

The first error is an easy fix, just change the first line in our story to:

// prev: import React from 'react';
import * as React from 'react'; 

The next few errors are asking us for type declarations for our imports, so TypeScript can do proper type checking. We can install these from npm, however I found the package names were a little unintuitive.

To do so:

npm install @types/storybook__react @types/storybook__addon-actions @types/storybook__addon-links

There's one more import for the demo components, but there's no definitions for this one. I'm a TypeScript noob, so the easiest thing to do was change the line to:

// prev: import { Button, Welcome } from '@storybook/react/demo';
const { Button, Welcome } = require('@storybook/react/demo');

And now we are back to a working Storybook, being compiled from TypeScript!

2017-08-12-16_54_53-Storybook

Typing things up

Now that we're compiling from TypeScript, we get all the nice autocomplete features and type safety. For example, if I update a story incorrectly, my editor tells me I messed up:

2017-08-12-16_58_24---index.tsx---storybook3-ts-example---Visual-Studio-Code

But it would be nice to do something a little more exciting. One of the things that Storybook offers is decorators which wrap stories with a commmon set of components.

I tried taking the centering decorator from the documentation and applying it to my example stories. To do this I created a new CenterDecorator.tsx file in src/decorators

From looking at the type definitions, I could see that .addDecorator() expects a RenderFunction, so I tell TypeScript I am supplying it:

import * as React from 'react';

import { RenderFunction } from '@storybook/react';

export const CenterDecorator = (story: RenderFunction) => (
  <div style={{textAlign: 'center'}}>
    {story()}
  </div>
);

Now coming back to index.tsx, I add the import statement and decorator:

// ...
import { CenterDecorator } from '../src/decorators/CenterDecorator';
// ...
storiesOf('Button', module)
  .addDecorator(CenterDecorator)

TypeScript is totally fine with all this, but heading back over to Storybook I noticed a problem:

2017-08-12-17_08_25-Storybook

This was a real headscratcher that took me around an hour to solve. It is probably pretty simple if you know Webpack at even the most basic level, but I clearly didn't :)

Heading back in to .storybook/webpack.config.js, all I need to add is a resolve key to handle tsx files:

module.exports = {
  // Add '.ts' and '.tsx' as resolvable extensions.
  resolve: {
    extensions: [".ts", ".tsx", ".js"]
  },
  module: {
      rules: [
          {
            test: /\.tsx/,
            loader: 'babel-loader!ts-loader',
            exclude: /node_modules/,
            include
          }
      ]
  }
};

As it's a change to the Webpack config, I once again had to stop and start Storybook again.

And finally, one last error:
2017-08-12-17_14_10-Storybook

As it turns out, this now upsets the TypeScript compiler which complains that our stories aren't in the default rootDir that create-react-app setup for us (which is src). This is an easy fix, by opening up tsconfig.json and updating:

{
  "compilerOptions": {
  ...
  // remove: "rootDir": "src",
  "rootDirs": ["src", "stories"],
  }
}

Finally, we have our centered stories and we can sleep easy again.

2017-08-12-17_18_04-Storybook

🙌🙌🙌

Outro

Hope this helped you get going with React, Storybook and TypeScript.
The source for my example repo is up on GitHub.