TypeScript-React-Starter README

The project's README from github.com/microsoft/TypeScript-React-Starter. As one markdown file: README.md.

This repo is now deprecated. In the time since it created TypeScript support is now a default feature of Create React App, Next.JS and Razzle.

This means you can get started with:

# Creates an app called my-app
npx create-react-app my-app --typescript

cd my-app

# Adds the type definitions
npm install --save typescript @types/node @types/react @types/react-dom @types/jest

echo "Good to go :tada:"

This repo offers some exmples on how to take that project into production and handle testing and state. However, you can also use the official documentation in the Create React App website for that.

If you'd like to know more about how to effectively do React with TypeScript, we recommend looking at the following:

Below is the original README for this sample.


TypeScript React Starter

This quick start guide will teach you how to wire up TypeScript with React. By the end, you'll have

We'll use the create-react-app tool to quickly get set up.

We assume that you're already using Node.js with npm. You may also want to get a sense of the basics with React.

Install create-react-app

We're going to use the create-react-app because it sets some useful tools and canonical defaults for React projects. This is just a command-line utility to scaffold out new React projects.

npm install -g create-react-app

Create our new project

We'll create a new project called my-app:

create-react-app my-app --scripts-version=react-scripts-ts

react-scripts-ts is a set of adjustments to take the standard create-react-app project pipeline and bring TypeScript into the mix.

At this point, your project layout should look like the following:

my-app/
├─ .gitignore
├─ images.d.ts
├─ node_modules/
├─ public/
├─ src/
│  └─ ...
├─ package.json
├─ tsconfig.json
├─ tsconfig.prod.json
├─ tsconfig.test.json
└─ tslint.json

Of note:

Setting up source control

Our testing tool, Jest, expects some form of source control (such as Git or Mercurial) to be present. For it to run correctly, we'll need to initialize a git repository.

git init
git add .
git commit -m "Initial commit."

Note: if you've cloned this repository, you won't have to run the above at all.

Overriding defaults

The TSLint configuration that react-scripts-ts sets us up with is a bit overzealous. Let's fix that up.

 {
-  "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
+  "extends": [],
+  "defaultSeverity": "warning",
   "linterOptions": {
     "exclude": [
       "config/**/*.js",
       "node_modules/**/*.ts"
     ]
   }
 }

Configuring TSLint is out of the scope of this starter, but you should feel free to experiment with something that works for you.

Running the project

Running the project is as simple as running

npm run start

This runs the start script specified in our package.json, and will spawn off a server which reloads the page as we save our files. Typically the server runs at http://localhost:3000, but should be automatically opened for you.

This tightens the iteration loop by allowing us to quickly preview changes.

Testing the project

Testing is also just a command away:

npm run test

This command runs Jest, an incredibly useful testing utility, against all files whose extensions end in .test.ts or .spec.ts. Like with the npm run start command, Jest will automatically run as soon as it detects changes. If you'd like, you can run npm run start and npm run test side by side so that you can preview changes and test them simultaneously.

Creating a production build

When running the project with npm run start, we didn't end up with an optimized build. Typically, we want the code we ship to users to be as fast and small as possible. Certain optimizations like minification can accomplish this, but often take more time. We call builds like this "production" builds (as opposed to development builds).

To run a production build, just run

npm run build

This will create an optimized JS and CSS build in ./build/static/js and ./build/static/css respectively.

You won't need to run a production build most of the time, but it is useful if you need to measure things like the final size of your app.

Creating a component

We're going to write a Hello component. The component will take the name of whoever we want to greet (which we'll call name), and optionally, the number of exclamation marks to trail with (enthusiasmLevel).

When we write something like <Hello name="Daniel" enthusiasmLevel={3} />, the component should render to something like <div>Hello Daniel!!!</div>. If enthusiasmLevel isn't specified, the component should default to showing one exclamation mark. If enthusiasmLevel is 0 or negative, it should throw an error.

We'll write a Hello.tsx:

// src/components/Hello.tsx

import * as React from 'react';

export interface Props {
  name: string;
  enthusiasmLevel?: number;
}

function Hello({ name, enthusiasmLevel = 1 }: Props) {
  if (enthusiasmLevel <= 0) {
    throw new Error('You could be a little more enthusiastic. :D');
  }

  return (
    <div className="hello">
      <div className="greeting">
        Hello {name + getExclamationMarks(enthusiasmLevel)}
      </div>
    </div>
  );
}

export default Hello;

// helpers

function getExclamationMarks(numChars: number) {
  return Array(numChars + 1).join('!');
}

Notice that we defined a type named Props that specifies the properties our component will take. name is a required string, and enthusiasmLevel is an optional number (which you can tell from the ? that we wrote out after its name).

We also wrote Hello as a stateless function component (an SFC). To be specific, Hello is a function that takes a Props object, and picks apart (or "destructures") all the properties that it will be passed. If enthusiasmLevel isn't given in our Props object, it will default to 1.

Writing functions is one of two primary ways React allows us to make components. If we wanted, we could have written it out as a class as follows:

class Hello extends React.Component<Props, object> {
  render() {
    const { name, enthusiasmLevel = 1 } = this.props;

    if (enthusiasmLevel <= 0) {
      throw new Error('You could be a little more enthusiastic. :D');
    }

    return (
      <div className="hello">
        <div className="greeting">
          Hello {name + getExclamationMarks(enthusiasmLevel)}
        </div>
      </div>
    );
  }
}

Classes are useful when our component instances have some state or need to handle lifecycle hooks. But we don't really need to think about state in this specific example - in fact, we specified it as object in React.Component<Props, object>, so writing an SFC makes more sense here, but it's important to know how to write a class component.

Notice that the class extends React.Component<Props, object>. The TypeScript-specific bit here are the type arguments we're passing to React.Component: Props and object. Here, Props is the type of our class's this.props, and object is the type of this.state. We'll return to component state in a bit.

Now that we've written our component, let's dive into index.tsx and replace our render of <App /> with a render of <Hello ... />.

First we'll import it at the top of the file:

import Hello from './components/Hello';

and then change up our render call:

ReactDOM.render(
  <Hello name="TypeScript" enthusiasmLevel={10} />,
  document.getElementById('root') as HTMLElement
);

Type assertions

One thing we'll point out in this section is the line document.getElementById('root') as HTMLElement. This syntax is called a type assertion, sometimes also called a cast. This is a useful way of telling TypeScript what the real type of an expression is when you know better than the type checker.

The reason we need to do so in this case is that getElementById's return type is HTMLElement | null. Put simply, getElementById returns null when it can't find an element with a given id. We're assuming that getElementById will actually succeed, so we need to convince TypeScript of that using the as syntax.

TypeScript also has a trailing "bang" syntax (!), which removes null and undefined from the prior expression. So we could have written document.getElementById('root')!, but in this case we wanted to be a bit more explicit.

Stateful components

We mentioned earlier that our component didn't need state. What if we wanted to be able to update our components based on user interaction over time? At that point, state becomes more important.

Deeply understanding best practices around component state in React are out of the scope of this starter, but let's quickly peek at a stateful version of our Hello component to see what adding state looks like. We're going to render two <button>s which update the number of exclamation marks that a Hello component displays.

To do that, we're going to

  1. Define a type for our state (i.e. this.state)
  2. Initialize this.state based on the props we're given in our constructor.
  3. Create two event handlers for our buttons (onIncrement and onDecrement).
// src/components/StatefulHello.tsx

import * as React from "react";

export interface Props {
  name: string;
  enthusiasmLevel?: number;
}

interface State {
  currentEnthusiasm: number;
}

class Hello extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { currentEnthusiasm: props.enthusiasmLevel || 1 };
  }

  onIncrement = () => this.updateEnthusiasm(this.state.currentEnthusiasm + 1);
  onDecrement = () => this.updateEnthusiasm(this.state.currentEnthusiasm - 1);

  render() {
    const { name } = this.props;

    if (this.state.currentEnthusiasm <= 0) {
      throw new Error('You could be a little more enthusiastic. :D');
    }

    return (
      <div className="hello">
        <div className="greeting">
          Hello {name + getExclamationMarks(this.state.currentEnthusiasm)}
        </div>
        <button onClick={this.onDecrement}>-</button>
        <button onClick={this.onIncrement}>+</button>
      </div>
    );
  }

  updateEnthusiasm(currentEnthusiasm: number) {
    this.setState({ currentEnthusiasm });
  }
}

export default Hello;

function getExclamationMarks(numChars: number) {
  return Array(numChars + 1).join('!');
}

Notice:

  1. Much like with Props, we had to define a new type for our state: State.
  2. To update state in React, we use this.setState - we don't set it directly except in the constructor. setState only takes the properties we're interested in updating and our component will re-render as appropriate.
  3. We're using class property initializers with arrow functions (e.g. onIncrement = () => ...).

We won't use this stateful component any further in this starter. Stateful components are great for creating components that focus solely on presenting content (as opposed to handling core application state). In some contexts, it can be used for handling your entire application's state, with one central component passing down functions that can call setState appropriately; however, for much larger applications, a dedicated state manager might be preferable (as we'll discuss below).

Adding style 😎

Styling a component with our setup is easy. To style our Hello component, we can create a CSS file at src/components/Hello.css.

.hello {
  text-align: center;
  margin: 20px;
  font-size: 48px;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

.hello button {
  margin-left: 25px;
  margin-right: 25px;
  font-size: 40px;
  min-width: 50px;
}

The tools that create-react-app uses (namely, Webpack and various loaders) allow us to just import the stylesheets we're interested in. When our build runs, any imported .css files will be concatenated into an output file. So in src/components/Hello.tsx, we'll add the following import.

import './Hello.css';

Writing tests with Jest

We had a certain set of assumptions about our Hello component. Let's reiterate what they were:

  • When we write something like <Hello name="Daniel" enthusiasmLevel={3} />, the component should render to something like <div>Hello Daniel!!!</div>.
  • If enthusiasmLevel isn't specified, the component should default to showing one exclamation mark.
  • If enthusiasmLevel is 0 or negative, it should throw an error.

We can use these requirements to write a few tests for our components.

But first, let's install Enzyme. Enzyme is a common tool in the React ecosystem that makes it easier to write tests for how components will behave. By default, our application includes a library called jsdom to allow us to simulate the DOM and test its runtime behavior without a browser. Enzyme is similar, but builds on jsdom and makes it easier to make certain queries about our components.

Let's install it as a development-time dependency.

npm install -D enzyme @types/enzyme enzyme-adapter-react-16 @types/enzyme-adapter-react-16 react-test-renderer

Notice we installed packages enzyme as well as @types/enzyme. The enzyme package refers to the package containing JavaScript code that actually gets run, while @types/enzyme is a package that contains declaration files (.d.ts files) so that TypeScript can understand how you can use Enzyme. You can learn more about @types packages here.

We also had to install enzyme-adapter-react-16 and react-test-renderer. This is something enzyme expects to be installed.

Before writing the first test, we have to configure Enzyme to use an adapter for React 16. We'll create a file called src/setupTests.ts that is automatically loaded when running tests:

import * as enzyme from 'enzyme';
import * as Adapter from 'enzyme-adapter-react-16';

enzyme.configure({ adapter: new Adapter() });

Now that we've got Enzyme set up, let's start writing our test! Let's create a file named src/components/Hello.test.tsx, adjacent to our Hello.tsx file from earlier.

// src/components/Hello.test.tsx

import * as React from 'react';
import * as enzyme from 'enzyme';
import Hello from './Hello';

it('renders the correct text when no enthusiasm level is given', () => {
  const hello = enzyme.shallow(<Hello name='Daniel' />);
  expect(hello.find(".greeting").text()).toEqual('Hello Daniel!')
});

it('renders the correct text with an explicit enthusiasm of 1', () => {
  const hello = enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={1}/>);
  expect(hello.find(".greeting").text()).toEqual('Hello Daniel!')
});

it('renders the correct text with an explicit enthusiasm level of 5', () => {
  const hello = enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={5} />);
  expect(hello.find(".greeting").text()).toEqual('Hello Daniel!!!!!');
});

it('throws when the enthusiasm level is 0', () => {
  expect(() => {
    enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={0} />);
  }).toThrow();
});

it('throws when the enthusiasm level is negative', () => {
  expect(() => {
    enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={-1} />);
  }).toThrow();
});

These tests are extremely basic, but you should be able to get the gist of things.

Adding state management

At this point, if all you're using React for is fetching data once and displaying it, you can consider yourself done. But if you're developing an app that's more interactive, then you may need to add state management.

State management in general

On its own, React is a useful library for creating composable views. However, React doesn't prescribe any specific way of synchronizing data throughout your application. As far as a React component is concerned, data flows down through its children through the props you specify on each element. Some of those props might be functions that update the state one way or another, but how that happens is an open question.

Because React on its own does not focus on application state management, the React community uses libraries like Redux and MobX.

Redux relies on synchronizing data through a centralized and immutable store of data, and updates to that data will trigger a re-render of our application. State is updated in an immutable fashion by sending explicit action messages which must be handled by functions called reducers. Because of the explicit nature, it is often easier to reason about how an action will affect the state of your program.

MobX relies on functional reactive patterns wher

TypeScript-React-Starter (microsoft/TypeScript-React-Starter)

Searching Database…

What is this?Microsoft's starter template and step-by-step guide for wiring up TypeScript with React. By the end you have a project with TSLint linting, Jest and Enzyme testing, and Redux state management.
What does the starter set up in a new project?It scaffolds a create-react-app project tuned for TypeScript via react-scripts-ts: tsconfig.json plus production and test variants, tslint.json, package.json scripts, public/ for static assets and src/ for your code.
How do I get a project up and running?Install create-react-app with npm, then scaffold with create-react-app my-app --scripts-version=react-scripts-ts. npm start spawns a dev server that reloads the page as you save files.
What do I need to have installed first?Node.js with npm is assumed. Jest also expects a source-control repo, so run git init before testing (not needed if you cloned this repository).
Is this still the recommended way to start a TypeScript + React app?No. The repo is archived and deprecated because TypeScript support is now a built-in feature of Create React App, Next.js and Razzle, so start new projects with one of those directly.
How do I run tests?npm run test runs Jest against every file ending in .test.ts or .spec.ts and automatically re-runs when files change, so you can run it side by side with npm start. Components are tested with Enzyme on top of jsdom, which simulates the DOM without a browser.
How do I build for production?Run npm run build: it creates an optimized, minified JS and CSS build in ./build/static/js and ./build/static/css. You rarely need it day to day; it is mainly useful for measuring the final size of your app.
Which TypeScript patterns does the guide cover?Typing component Props with required and optional fields, stateless function components versus class components typed as React.Component , updating state through setState, and type assertions like document.getElementById('root') as HTMLElement to tell the checker an expression is not null.
When do I actually need the Redux part?If your app just fetches data once and displays it, you can skip it. The starter wires in redux and react-redux for interactive apps where state has to be shared across components, noting that much larger applications usually want a dedicated state manager.
What should I use instead of this starter today?Create React App, Next.js or Razzle, which now support TypeScript out of the box. To go deeper, the README recommends the React+TypeScript Cheatsheets and the React & Redux in TypeScript static typing guide.
Creator
microsoft
Size
0.2 MB
Stars
11K
Updated
CommitAug 23
Age
9y
Commits · 90 Days