[TypeScript][Express] Try React
Tags: typescript, react, express
Published: 2021-07-17 00:27:35

## Intro
This time, I will tye React to get and show book data from the Express application.

* [[TypeScript][PostgreSQL][TSyringe][Express]Try TypeORM 2](https://dev.to/masanori_msl/typescript-postgresql-tsyringe-express-try-typeorm-2-3bka)

### Environments
* Node.js ver.16.5.0
* create-react-app ver.4.0.3
* React ver.17.0.2
* react-router-dom ver.5.2.0
* TypeScript ver.4.3.5
* ESLint ver.7.30.0

According to the tutorials, I created a React project by create-react-app.

```
npx create-react-app bookstore-sample-client --template typescript
```

* [Tutorial: Intro to React - React](https://reactjs.org/tutorial/tutorial.html)

## Use ESLint
This time, I also try using ESLint.

```
npx eslint --init
```

And I got errors in TSX files.

```
'React' must be in scope when using JSX
```

According to some posts, I add a rule in .eslitrc.yml

#### .eslitrc.yml
```yml
env:
  browser: true
  es2021: true
extends:
  - 'eslint:recommended'
  - 'plugin:react/recommended'
  - 'plugin:@typescript-eslint/recommended'
parser: '@typescript-eslint/parser'
parserOptions:
  ecmaFeatures:
    jsx: true
  ecmaVersion: 12
  sourceType: module
plugins:
  - react
  - '@typescript-eslint'
rules: {
  "react/react-in-jsx-scope": "off",
}
```

* [react.js - 'React' must be in scope when using JSX react/react-in-jsx-scope? - Stack Overflow](https://stackoverflow.com/questions/42640636/react-must-be-in-scope-when-using-jsx-react-react-in-jsx-scope)
* [Prevent missing React when using JSX (react/react-in-jsx-scope) - GitHub](https://github.com/yannickcr/eslint-plugin-react/blob/HEAD/docs/rules/react-in-jsx-scope.md)

## Routing
Although I just add only one page in this sample, I try routing as a SPA by "react-router-dom".

#### [Client] App.tsx
```tsx
import './App.css';
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link
} from "react-router-dom";
import { SearchBooks } from './search/SearchBooks';

function App(): JSX.Element {
  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
          </ul>
        </nav>
        <Switch>
          <Route path="/">
            <SearchBooks />
          </Route>
        </Switch>
      </div>
    </Router>
  );
}
export default App;
```

* [React Router: Declarative routing for React.js](https://reactrouter.com/web/guides/quick-start)
* [ReactTraining / react-router - GitHub](https://github.com/ReactTraining/react-router)

## [Express] Use CORS
Because the domains aren't same as the Express app and the React app, I have to allow origins in the Express app.

#### [Server] index.ts
```ts
import "reflect-metadata";
import express from 'express';
import cors from 'cors';
import { container } from 'tsyringe';
import { BookService } from "./books/bookService";

const port = 3099;
const app = express();

const allowlist = ['http://localhost:3000', 'http://localhost:3099']
const corsOptionsDelegate: cors.CorsOptionsDelegate<any> = (req, callback) => {
  const corsOptions = (allowlist.indexOf(req.header('Origin')) !== -1)? { origin: true }: { origin: false };
  callback(null, corsOptions);
};
...
app.get('/books', cors(corsOptionsDelegate), async (req, res) => {
    const books = container.resolve(BookService);
    res.json(await books.getBooks());
    
});
app.listen(port, () => {
    console.log(`Example app listening at http://localhost:${port}`)
});
```

#### [Client] BookAccessor.ts
```ts
import { Book } from "../models/book";

export async function search(): Promise<Book[]> {    
    return await fetch('http://localhost:3099/books', {
        method: 'GET',
    })
    .then(response => response.json())
    .then(json => JSON.parse(JSON.stringify(json)))
    .catch(err => console.error(err));
}
```

* [Express cors middleware](http://expressjs.com/en/resources/middleware/cors.html)
* [Access-Control-Allow-Origin - HTTP|MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin)
* [cors - Enable Access-Control-Allow-Origin for multiple domains in Node.js - Stack Overflow](https://stackoverflow.com/questions/24897801/enable-access-control-allow-origin-for-multiple-domains-in-node-js)
* [express.jsのcors対応 - Qiita](https://qiita.com/chenglin/items/5e563e50d1c32dadf4c3)

## Create pages
This time, I just add simple pages.

#### [Client] SearchBooks.tsx
```tsx
import { useState } from "react";
import './SearchBooks.css';
import { Book } from "../models/book";
import { SearchBookRow } from "./SearchBookRow";
import * as bookAccessor from './BookAccessor';

export function SearchBooks(): JSX.Element {
    const [books, setBooks] = useState([] as Array<Book>);
    const generateRows = () => {
        const contents: JSX.Element[] = [];
        for(const b of books) {
            contents.push(<SearchBookRow key={b.id} book={b}></SearchBookRow>);
        }
        return contents;
    };
    return <div className="search_result_area">
        <button onClick={async () => {
            setBooks(await bookAccessor.search());
        }}>Search</button>
        {generateRows()}
    </div>
}
```
#### [Client] SearchBooks.css
```css
.search_result_row {
    background-color: aqua;
}
```

#### [Client] SearchBookRow.tsx
```tsx
import { Book } from "../models/book";

export type SearchBookRowProps = {
    book: Book
};
export function SearchBookRow(props: SearchBookRowProps): JSX.Element {

    return <div className="search_result_row">
        <div className="search_result_row_cell">{props.book.name}</div>
        <div className="search_result_row_cell">{props.book.author.name}</div>
        <div className="search_result_row_cell">{props.book.genre.name}</div>
        <div className="search_result_row_cell">{props.book.price}</div>
    </div>
}
```

* [Introducing Hooks - React](https://reactjs.org/docs/hooks-intro.html)
* [Tutorial: Intro to React - React](https://reactjs.org/tutorial/tutorial.html)
* [A Beginner’s Guide to Loops in React JSX](https://www.telerik.com/blogs/beginners-guide-loops-in-react-jsx)

### className
According to the result, the CSS class names what are generated from "className" won't be changed automatically.
So I can write child components' CSS in parent's CSS like the sample.

If I add same CSS code in child components' CSS files, the child components use them.