[TypeScript][Express] Try React

masanori_msl

Masui Masanori

Posted on July 17, 2021

[TypeScript][Express] Try React

Intro

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

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
Enter fullscreen mode Exit fullscreen mode

Use ESLint

This time, I also try using ESLint.

npx eslint --init
Enter fullscreen mode Exit fullscreen mode

And I got errors in TSX files.

'React' must be in scope when using JSX
Enter fullscreen mode Exit fullscreen mode

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

.eslitrc.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",
}
Enter fullscreen mode Exit fullscreen mode

Routing

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

[Client] App.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;
Enter fullscreen mode Exit fullscreen mode

[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

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}`)
});
Enter fullscreen mode Exit fullscreen mode

[Client] BookAccessor.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));
}
Enter fullscreen mode Exit fullscreen mode

Create pages

This time, I just add simple pages.

[Client] SearchBooks.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>
}
Enter fullscreen mode Exit fullscreen mode

[Client] SearchBooks.css

.search_result_row {
    background-color: aqua;
}
Enter fullscreen mode Exit fullscreen mode

[Client] SearchBookRow.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>
}
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
masanori_msl
Masui Masanori

Posted on July 17, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

AILingo
devchallenge AILingo

November 25, 2024

[TypeScript][Express] Try React 2
typescript [TypeScript][Express] Try React 2

August 13, 2021

[TypeScript][Express] Try React
typescript [TypeScript][Express] Try React

July 17, 2021