DEV Community

Masui Masanori
Masui Masanori

Posted on

1

[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.

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Instrument, monitor, fix: a hands-on debugging session

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️