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.
Top comments (0)