[TypeScript][Express] Try React
Masui Masanori
Posted on July 17, 2021
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
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
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
- Prevent missing React when using JSX (react/react-in-jsx-scope) - GitHub
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;
[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}`)
});
[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));
}
- Express cors middleware
- Access-Control-Allow-Origin - HTTP|MDN
- cors - Enable Access-Control-Allow-Origin for multiple domains in Node.js - Stack Overflow
- express.jsのcors対応 - Qiita
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>
}
[Client] SearchBooks.css
.search_result_row {
background-color: aqua;
}
[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>
}
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.
Posted on July 17, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.