React Typescript con StoryBook
Tags: react, typescript, tutorial, storybook
Published: 2020-06-30 23:40:44

Como primer paso después de hacer un CRA (create-react-app), instalo Storybook y Bit; los dos me permiten tener mi documentación, toolbox, y sincronización de mis componentes a través de todos mis proyectos.

Recientemente estoy migrando mis apps y las nuevas que voy realizando en React con Typescript.  A continuación los pasos para configurar correctamente (según mi manera de ver) React con Typescript y Storybook.

#1. Crear el Proyecto con Soporte de Typescript.
Supongamos que mi proyecto se llama MiProyecto:
```
npx create-react-app MiProyecto --template typescript
```
Con esto ya tendré mi proyecto configurado en Typescript, pero aun faltan algunas configuraciones para que en VSCode trabaje de la manera correcta:

##Instalar dependencias
A continuación estas son todas las dependencias para una integración correcta de Typescript en el proyecto creado:
```
npm install -D @typescript-eslint/eslint-plugin @typescript-eslint/parser babel-eslint eslint eslint-config-airbnb  eslint-config-prettier eslint-plugin-import  eslint-plugin-jest eslint-plugin-jsx-a11y eslint-plugin-prettier eslint-plugin-react  husky lint-staged prettier react-test-renderer require-context.macro tslint tslint-config-prettier tslint-plugin-prettier tslint-react 
```
Aquí hay algunas instaladas preparándonos para Storybook.  Debemos crear algunos archivos para la configuración del linter en el root del proyecto:
###.env
```typescript
SKIP_PREFLIGHT_CHECK=true
```
###.eslintrc.js
```javascript
module.exports = {
  parser: '@typescript-eslint/parser', // Specifies the ESLint parser
  extends: [
    'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react
    'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
    'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
    'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
  ],
  parserOptions: {
    ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
    sourceType: 'module', // Allows for the use of imports
    ecmaFeatures: {
      jsx: true, // Allows for the parsing of JSX
    },
  },
  rules: {
    // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
    // e.g. "@typescript-eslint/explicit-function-return-type": "off",
  },
  settings: {
    react: {
      version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use
    },
  },
};
```
###.prettierrc
```json
{
  "printWidth": 120,
  "singleQuote": true,
  "trailingComma": "es5",
  "tabWidth": 2
}
```
###tsconfig.json
```json
{
  "compilerOptions": {
    "target": "ES2016",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true, // Allow JavaScript files to be compiled
    "skipLibCheck": true, // Skip type checking of all declaration files
    "esModuleInterop": true, // Disables namespace imports (import * as fs from "fs") and enables CJS/AMD/UMD style imports (import fs from "fs")
    "allowSyntheticDefaultImports": true, // Allow default imports from modules with no default export
    "strict": true, // Enable all strict type checking options
    "forceConsistentCasingInFileNames": true, // Disallow inconsistently-cased references to the same file.
    "module": "esnext", // Specify module code generation
    "moduleResolution": "node", // Resolve modules using Node.js style
    "isolatedModules": true,
    "resolveJsonModule": true, // Include modules imported with .json extension
    "noEmit": true, // Do not emit output (meaning do not compile code, only perform type checking)
    "jsx": "react", // Support JSX in .tsx files
    "sourceMap": true, // Generate corrresponding .map file
    "declaration": true, // Generate corresponding .d.ts file
    "noUnusedLocals": true, // Report errors on unused locals
    "noUnusedParameters": true, // Report errors on unused parameters
    "experimentalDecorators": true, // Enables experimental support for ES decorators
    "noFallthroughCasesInSwitch": true // Report errors for fallthrough cases in switch statement
  },
  "include": [
    "src/**/*" // *** The files TypeScript should type check ***
  ],
  "exclude": ["node_modules", "build"], // *** The files to not type check ***
  "plugins": [{ "name": "typescript-tslint-plugin" }]
}
```
###tslint.json
```json
{
  "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
  "rulesDirectory": ["tslint-plugin-prettier"],
  "rules": {
    "prettier": true,
    "interface-name": false
  },
  "linterOptions": {
    "exclude": ["config/**/*.js", "node_modules/**/*.ts", "coverage/lcov-report/*.js"]
  }
}
```
y en el Package debemos configurar:
```json
scripts: {
...
    "lint-ts": "tslint -c tslint.json 'src/**/*.{ts,tsx}'",
    "lint-js": "eslint 'src/**/*.{js,jsx}' --quiet --fix",
    "lint": "tslint -c tslint.json src/**/*.{ts,tsx} --fix --format verbose",
...
},
...
"husky": {
    "hooks": {
      "pre-commit": "export CI=true && yarn build && lint-staged && yarn test",
      "pre-push": "export CI=true && yarn build && lint-staged && yarn test"
    }
  },
  "lint-staged": {
    "*.{ts,tsx}": [
      "tslint -c tslint.json"
    ],
    "*.{js,jsx}": [
      "eslint --fix"
    ]
  }
...
```
Ejecutamos los linters para verificar que no tenemos errores, en caso de encontrarlos corregirlos.
```
npm run lint-ts
npm run lint-js
npm run lint
```

#2. Instalar Storybook
Realizamos la instalación de Storybook, aqui tambien estan los addons que he empleado para mi instalación en este "boiler-plate" (importante tener instalado el cli de Storybook):
```shell
npx -p @storybook/cli sb init --story-format=csf-ts
npm install -D @storybook/addon-actions @storybook/addon-docs @storybook/addon-knobs @storybook/addon-links @storybook/addon-storyshots @storybook/preset-create-react-app @storybook/react @types/storybook__addon-knobs @types/storybook__addon-storyshots @types/storybook__reac 
```
Creamos un folder que llamaremos .storybook.  En el crearemos 4 archivos:
###addon.ts
```typescript
import '@storybook/addon-actions/register';
import '@storybook/addon-links/register';
import '@storybook/addon-docs/register';
import '@storybook/addon-knobs/register';
```
###config.ts
```typescript
import { configure } from '@storybook/react';
import requireContext from 'require-context.macro';
const req = requireContext('../src', true, /\.stories\.tsx$/);
function loadStories() {
  req.keys().forEach(req);
}
configure(loadStories, module);
```
###main.ts
```typescript
module.exports = {
  stories: ['../src/**/*.stories.(ts|tsx|js|jsx)'],
  addons: [
    '@storybook/addon-actions',
    '@storybook/addon-links',
    '@storybook/addon-knobs',
    '@storybook/preset-create-react-app',
    {
      name: '@storybook/addon-docs',
      options: {
        configureJSX: true,
      },
    },
  ],
};
```
###webpack.config.ts
```typescript
module.exports = ({ config, mode }) => {
  config.module.rules.push({
    test: /\.(ts|tsx)$/,
    loader: require.resolve('babel-loader'),
    options: {
      presets: [['react-app', { flow: false, typescript: true }]],
    },
  });
  config.resolve.extensions.push('.ts', '.tsx');
  return config;
};
```
y en nuestro package.json adicionar estas dos opciones:
```json
...
scripts: {
...
    "storybook": "start-storybook -p 9009 -s public",
    "build-storybook": "build-storybook -s public",
...
},
...
```
Ya estaria lista la configuracion; pero para probarlo corriendo debemos crear un componente, en este caso creamos un componente muy sencillo, como un boton con dos colores amarillo, y verde.
###./src/components/buttons/Button/Button.tsx
```typescript
import React from 'react';
// ?Button Component
export interface IProps {
  color: string;
  onClick?: (color: string) => void;
}
export default (props: IProps) => {
  const { color, onClick } = props;

  const handleClick = (): void => {
    if (onClick) onClick(color);
  };

  return (
    <button style={{ color }} onClick={handleClick}>
      Color Button
    </button>
  );
};
```
Este sería nuestro componente pero para visualizarlo en StoryBook necesitamos crear el Storie para este componente:
###./src/components/buttons/Button/Button.stories.tsx
```typescript
import React from 'react';
import { storiesOf } from '@storybook/react';
import Button from './Button';

import { action } from '@storybook/addon-actions';
import { withKnobs, text, select } from '@storybook/addon-knobs';

storiesOf('Button', module)
  .addDecorator(withKnobs)
  .add('Amarillo', () => (
    <Button
      color={select('color', { Amarillo: 'yellow', Naranja: 'orange' }, 'yellow')}
      onClick={action('Presiono Click')}
    />
  ))
  .add('Verde', () => <Button color={text('color', 'green')} onClick={action('clicked')} />);
```
Con esto ya creado, vamos a correr nuestro servidor de Storybook para visualizar el componente:
```
yarn storybook
```
![yarn storybook](https://dev-to-uploads.s3.amazonaws.com/i/rrwt0yceoosrubg18b5h.png)
En el navegador veriamos:
![Storybook](https://dev-to-uploads.s3.amazonaws.com/i/ynd570vnqashrx03nh8j.png)
Podemos cambiar el color de acuerdo a lo que configuramos, ver cuando presionamos el botón, ver todos los componentes, y mas.
![Storybook](https://dev-to-uploads.s3.amazonaws.com/i/9g0wdso5viww9l0eymii.png)![Storybook](https://dev-to-uploads.s3.amazonaws.com/i/63nrvjnndxqx5hzfdaqg.png)![Storybook](https://dev-to-uploads.s3.amazonaws.com/i/wwknkkgp4h3vqjrl3oqe.png)
Ya estamos entonces listos con un proyecto inicial en React, con Typescript, y StoryBook.

Instalaremos Bit, y configuraremos estos componentes también en su versión con Styled Components en un próximo Post.

PS: Sigueme, comparte, comment y da Like si te sirvio este Post.