#### Advanced Topics on React.js and React Router

Spring 2019 。 Ric Huang
---
### Example in previous meeting...

----
### In "index.js"
```jsx
import Counter from './containers/Counter';
ReactDOM.render(<Counter / >, document.getElementById('root'));
```
----
### In "Containers/Counter.js"
```jsx
import Button from "../components/Button";
import Input from '../components/Input';
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 100 };
}
... some methods for class Counter
render() {
return (
<div>
<h1>{this.state.count}</h1>
<span>
<Button text="+" onClick={this.handleInc} />
<Button text="-" onClick={this.handleDec} />
<Input onKeyPress={this.handleInput} />
</span>
</div>
);
}
}
```
----
```jsx
// In Components/Button.js
import React from 'react'
export default ({ onClick, text }) => {
return <button onClick={onClick}>{text}</button>;
}
// In Components/Input.js
import React from 'react';
export default ({onKeyPress}) => {
return <input type="text"
placeholder="Enter a number..."
onKeyPress={onKeyPress}
/>;
}
```
----
```jsx
class Counter extends Component {
...
handleInc = () => this.setState(state => ({ count: state.count + 1 }));
handleDec = () => this.setState(state => ({ count: state.count - 1 }));
setNumber = num => this.setState(() => ({ count: num }));
handleInput = e => {
if (e.key === "Enter") {
const value = parseInt(e.target.value);
if (value === 0 || value) this.setNumber(value);
e.target.value = "";
e.target.blur();
}
};
...
}
```
---
### Advanced Topics on React.js
1. 善用 Array and arrow functions ([ref](https://reactjs.org/docs/lists-and-keys.html))
2. Composition and props.children ([ref](https://reactjs.org/docs/composition-vs-inheritance.html))
3. React.Fragment ([ref](https://reactjs.org/docs/fragments.html))
4. Higher Order Component (HOC) ([ref](https://reactjs.org/docs/higher-order-components.html))
5. React Refs ([ref](https://reactjs.org/docs/refs-and-the-dom.html))
6. Forwarding refs ([ref](https://reactjs.org/docs/forwarding-refs.html))
---
### 善用 Array and arrow functions
----
### [範例] TodoList.js
```jsx
import classes from './TodoList.module.css'
import TodoItem from './TodoItem/TodoItem'
export default ({ items, toggleItem, removeItem, save }) => {
if (!items.length) return null;
return (
<ul className={classes.List}>
{items.map((ele, key) => (
<TodoItem item={ele} key={key} identity={key}
toggleItem={toggleItem}
removeItem={removeItem} save={save} / >
))}
</ul>
);
}
```
----
* 在前頁的例子
```jsx
return (
<ul>{an array of React Components}</ul>
)
```
* 而 array of React Components 則是用 ---
```jsx
Array.map( element => (some JSX expression) );
```
來實現
----
#### "key" for React array of components
* 請注意:身為一個新手,當你產生一個 array of components 的時候,你很可能會得到 warning 說:*Warning: Each child in a list should have a unique "key" prop.*
* 這是因為對於 listed items, React 是用內建的 global attribute "key" 來決定哪些 items 有被修改或是增減,所以如果你沒有 specify "key", 沒有加上 "key" attribute, 你的 React Component 的行為可能會跟你的預期不同
* Note: 不過要小心 "key" 的 value 要維持 unique, 不可以有不同的 listed items 有相同的 keys。更多 details, 可以看看 React 的 VDOM 如何察覺變化 ([ref](https://reactjs.org/docs/reconciliation.html))
---
### Compositional Model
* 想像一下,在某個 blog site, 隨著瀏覽頁面不同,你看到的內容也會不一樣。不過,這些頁面似乎都是由一些基本元件所組成 --- memnu bar, article, images, comments, etc. 這種由一些 components 來組成頁面的設計方法,是 React 裡面很重要也很基本的 "Compositional Model"
----
### props.children
* 不過,在 compositional model 裏頭的 child components 並不是 listed items, 並且在宣告的時候,並沒有確定有哪些 children, 而是在應用時才被 instantiated. 例如:
```html
<h1> This is a title </h1>
<p> This is a paragraph </p>
<div> This is a div </div>
<footer> This is a footer </footer>
```
----
* 這時候你可以用內建的 **"props.children"** 來宣告一個 "黑盒子",讓 instantiate 這個 component 的物件來決定 children 由哪些 components 來組成:
```jsx
class BlogPage extends Component {
render () {
return
<div className="blog-page">
{this.props.children}
</div>;
}
}
```
----
// In "App.js"
```jsx
class App extends Component {
render() {
const contents = [];
contents.push(<h1> This is a title </h1>);
contents.push(<p> This is a paragraph </p>);
contents.push(<div> This is a div </div>);
contents.push(<footer> This is a footer </footer>);
return <BlogPage children={contents} />;
}
}
```
---
### React.Fragment
* Recall: 在 React Component 的 render() 裏頭,你必須 return 一個 single root node. 但當我們需要 return multiple nodes 的時候,一個解法是用 "\<div>\</div>" 包起來。但是,如果 caller 的 children 不可以是 "\<div>" 怎麼辦呢?
```jsx
class MyTable extends Component {
render() {
return
<table>
<MyData dataInput={data1}/ >
<MyData dataInput={data2}/ >
</table>
}
}
```
----
### Oops, \<tr> is expected...
```jsx
class MyData extends Component {
render() {
return (
<div>
<tr>{some data}</tr>
<tr>{some data}</tr>
</div>
);
}
}
```
----
### Use React.Fragment to solve it!
```jsx
import React, { Fragment } from 'react';
class MyData extends Component {
render() {
return (
<Fragment>
<tr>{some data}</tr>
<tr>{some data}</tr>
</Fragment>
);
}
}
```
----
* It can also be written as...
```jsx
import React, { Fragment } from 'react';
class MyData extends Component {
render() {
return (
<>
<tr>{some data}</tr>
<tr>{some data}</tr>
</>
);
}
}
```
* However, some browsers may not yet support this short form yet. Use it with care!
* Note: "key" is the only attribute that can be used in \<Fragment>. Event handlers are not supported yet.
---
### Higher Order Component (HOC)
* **"Higher Order Component"** 是另外一個 React 裡頭常用的 programming technique --- 想像你的 nevigation bar 會隨著 logged in user 不同而有不同的內容/layout,或者是你的 blog page 會隨著文章種類的不同而選擇不同的來源... 等,但他們的 event binding, error handling, 或者是其他的邏輯是一樣的,所以你會想要有一個 **"產生 component 的 function"**, 可以吃進一個 component 當參數,然後也許吃進另一個 callback 當作客製化 layout/data source 的方法,像這樣 (next page):
----
```jsx
const generalNavBar = (WrappedNavBar, layoutMethod) =>
return class extends Component {
constructor(props) {...}
... some life-cycle methods or event handling logic
render() {
return <WrappedNavBar ... / >;
}
}
```
* Note:
1. 基本上, HOC 就是用一個 function, 吃進一個 wrapped component, 產生另一個 higher-order component
2. 參數列不限個數,但第一個 arg 通常是 wrapped component
3. "return class extends" <== anonymous class
----
### Another HOC example
```jsx
const withAuthGuard = WrappedComponent => {
return class extends Component {
render() {
return (
<Query query={ME_QUERY}
onError={error => {...; }}}> {
({ data, loading, error }) => {
if (loading) return <WrappedComponent loading={true} />
if (error) return <WrappedComponent isAuth={false} />
return (
<WrappedComponent isAuth={true} username={data.me.username} />)}
} </Query>)}
}
}
```
----
### HOC should be pure!
* Note that a HOC doesn’t modify the input component, nor does it use inheritance to copy its behavior. Rather, a HOC composes the original component by wrapping it in a container component. A HOC is a pure function with zero side-effects.
* However, don't apply HOC in render(). This will cause the subtree to unmount/remount each time! ([ref](https://reactjs.org/docs/higher-order-components.html#dont-use-hocs-inside-the-render-method))
---
### React Refs
* 當你要從介面去改動一個很深的 component 的時候,正常來說你要透過一層層 props 的傳遞,才能把要改的資料/執行的動作傳到底層的元件。但,有時候這樣寫不是很直觀、或者是當牽涉到你不應該去改動的第三方 modules 的時候,一個有點暴力、雖然不建議但偶爾可以用一下的方法就是 create 一個 React ref, 就像是一個 endpoint, 讓外部的使用者可以直接 access 到這個 DOM node/component
* 很明顯的,這樣做會破壞 encapsulation, 所以在加入 React Ref 之前,應該要思考一下是否真的有必要這麼做? (我們未來會再教一些比較好、安全的做法)
----
### React Refs Example
```jsx
class App extends Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
focusTextInput = () => { this.textInput.current.focus(); };
render() {
return (
<div>
<textarea rows="4" cols="50" ref={this.textInput} / >
<input type="button" value="Click to edit"
onClick={this.focusTextInput} />
</div>
);
}
}
```
----
* In the previous example ---
```jsx
class App extends Component {
constructor(props) {
super(props);
// 產生一個 React ref, 先把它存在 this.textInput 裡
this.textInput = React.createRef();
}
...
render() {
return (
<div>
// 產生一個 <textarea> element, 並且把存在
// this.textInput 的 reference 指到這個 element
<textarea ... ref={this.textInput} / >
...
</div>
);
}
}
```
----
* Previous example (continued)
```jsx
class App extends Component {
...
render() {
return (
<div>
...
// 當這個 input button 被 click 的時候,
// 呼叫 this.focusTextInput
<input type="button" value="Click to edit"
onClick={this.focusTextInput} />
</div>
);
}
}
```
----
* Previous example (continued)
```jsx
class App extends Component {
...
// 當 focusTextInput 被呼叫的時候,用 this.textInput.current
// 抓到目前被 referred 的 element, 也就是下面的 <textArea>,
// 並且執行它的 .focus()
focusTextInput = () => { this.textInput.current.focus(); };
render() {
return (
<div>
<textarea rows="4" cols="50" ref={this.textInput} / >
...
</div>
);
}
}
```
----
* 如果有兩個 elements 都設定 refs... 請分別 createRef()
```jsx
class App extends Component {
constructor(props) {
super(props);
this.textInput1 = React.createRef();
this.textInput2 = React.createRef();
}
...
render() {
return (
<div>
<textarea ... ref={this.textInput1} / >
<textarea ... ref={this.textInput2} / >
...
</div>
);
}
}
```
---
### Forwarding Refs
* 有時候必須將 local 拿到 child or HOC 的 component 來作為 ref, 以利在 local 做一些直接的操作
* 語法:
```jsx
const SomeComponent = React.forwardRef(
(props, ref) => (<ChildComponent ref={ref} ... / >)
);
class App extends Component {
... this.ref = React.createRef();
// this.ref.current will become <ChildComponent>
render() {
return (
<SomeComponent ref={this.ref}... / >
);
}
}
```
----
### 完整範例
```jsx
const MyDiv = React.forwardRef((props, ref) => (
<div ref={ref} className="MyDiv">{props.children}</div>
));
class App extends Component {
constructor(props) {
super(props);
this.ref = React.createRef();
}
componentDidMount() { console.log(this.ref.current); }
render() {
return (
<MyDiv ref={this.ref}>
...
</MyDiv>
)}
}
```
---
學習完一些 advanced React topics 之後,
我們接下來要來學 **React 生態系** 裡頭一個很基礎,
也很重要的部分:**React Router**
---
### React Router
----
### Motivations and Backgrounds: Server-Side (後端) vs Client-Side (前端) Rendering
* Recall: 我們這邊講的**前端**是指你所使用的瀏覽器,負責收到 HTML & data 以後顯示出網頁,而**後端**是指 Web Server, 在收到 http request 之後一方面視需求到資料庫存取、修改資料,然後把 HTML & data 回傳給前端顯示
----
### Server-Side (後端) vs Client-Side (前端) Rendering
* **"Render"** 一般翻譯成 **"渲染"** 在網頁上就是把頁面畫出來的意思。當使用者點選連結、切換頁面的時候,到底是後端把整 HTML 處理好之後,再傳給前端畫出來 (i.e. server-side rendering),還是後端只把必要資料處理好之後傳給前端,再由前端處理產生 HTML 再畫出來 (i.e. client-side rendering) 呢?
----
### Server-Side (後端) Rendering
* 如果使用者開啟一個新的網址,server-side rendering 會讓前端拿到一個新的 HTML,他會看到畫面刷新,如果網路 lag 或是網頁寫得不夠好的話,甚至會看到「白畫面」
* 想想如果是在聽音樂、玩遊戲,這樣的體驗當然很不 OK!
----
### Server-Side Rendering ([圖示](https://blog.techbridge.cc/2017/09/16/frontend-backend-mvc/))

----
### Client-Side (前端) Rendering
* Clinet-side rendering 就是利用一些像是 VDOM 的技術,讓使用者點選一些連結的時候,前端網頁只是透過 API 像後端要資料,而前端拿回資料後再更新 DOM 需要更新的 HTML, 動態的更新那部份的頁面。
* 這樣的做法通常會讓前端的 code 變得複雜許多,但還好現在許多前端技術 (e.g. React Routing, GraphQL) 讓這一切寫起來比較乾淨、也比較模組化
----
### Client-Side Rendering ([圖示](https://blog.techbridge.cc/2017/09/16/frontend-backend-mvc/))

----
### SPA (Single Page Application)
* 不過前述的 client-side rendering 常常配合著所謂的 SPA (Single Page Application) 的實現方法,也就是說,前端事實上只有一個 index.html, 所以使用者在切換連結的時候只是發出 Ajax/JSON API request, 從後端拿資料,然後前端並沒有切換頁面,所以可以做到像是使用者一邊在網頁上看影片,一方面點選頁面上的連結去查看作者、影片相關資訊,而不會影響到影片的播放。
----
### Client-Side Routing
* 不過再想像一個情況,假設你在瀏覽一個部落格或是論壇,從一篇文章切換到另外一篇文章,由於 client-side rendering 的關係,所以頁面上者有文章更新的部分被 update, 所以看起來很順。
* 但問題是,當你很直覺的按瀏覽器的「上一頁」,想要回到上一篇文章的時候,你會發現沒有用!因為你從頭到尾都是在 "index.html" 這頁上面啊!
----
### Client-Side Routing
* Client-Side Routing 讓你在 local 端產生瀏覽器的 routing, 像是:
* ...myblog.com/home
* ...myblog.com/posts
* ...myblog.com/posts/13
* ...myblog.com/users/ric
* 在瀏覽到不同頁面的時候會有對應到不同的 routing (web address),而被存到瀏覽器的 history 中,可以使用前/後一頁
----
#### Client-Side Rendering/Routing ([圖示](https://blog.techbridge.cc/2017/09/16/frontend-backend-mvc/))

---
### React Router
* React Router 是整個 「React 生態系」的一部分,通過管理 URL,實現頁面以及狀態切換可以「模組化」,讓 code 更好管理,也比較容易理解,也能符合 React 基本只更新 minimum difference 的概念
----
### 安裝與使用 React Router
* 安裝:npm install react-router-dom
* 如果遇到建議要 "npm audit fix",就 fix 吧!
* 使用:
```jsx
import { BrowserRouter } from 'react-router-dom'
import { NavLink, Switch, Route } from 'react-router-dom'
```
----
### 一個簡單的應用情境
* 假設你寫了一個 blog page, 你規劃了:
* '/' or '/home':主畫面
* '/posts':顯示所有文章列表
* '/posts/\<postId>':顯示某篇文章
* '/authors':顯示所有作者列表
* '/authors/\<authorName>':顯示某位作者的文章列表
----
### Top-level "App.js"
```jsx
class App extends Component {
render() {
return (
<BrowserRouter>
<div className="App">
<Blog / >
</div>
</BrowserRouter>
)
}
}
```
* 定義了這個 APP 的 root directory (i.e. '/')
----
### In "Blog.js"
```jsx
class Blog extends Component {
render() {
return (
<div> // Define your blog layout
...
<NavLink to="/home">Home</NavLink>
<NavLink to="/posts">Posts</NavLink>
<NavLink to="/authors">Authors</NavLink>
...
<Switch>
<Route exact path="/posts" component={Posts} / >
<Route path="/posts/:id?" component={PostRoute} / >
<Route exact path="/authors" components={Authors} / >
<Route path="/authors/:name?" components={AuthorRoute} / >
<Redirect from="/Home" to="/" / >
</Switch>
</div>
)
}
}
```
----
#### \<NavLink to="/home">Home\</NavLink>
* 定義 "Home" 這個字所對應的 routing path
* 其中,'/' 代表這個 App 的根目錄
* **\<NavLink>** 只是用來代表一個 link 而已,真正在頁面上畫出來,還是要在外面包一個 HTML tag, 例如:
* \<p>\<NavLink to="/home">Home\</NavLink>\</p>
* \<li>\<NavLink to="/home">Home\</NavLink>\</li>
* \<button>\<NavLink to="/home">Home\</NavLink>\</button>
* 換句話說,在畫面點下 "Home" 的時候,頁面會 route 到 ""...AppHome/home"
----
### \<NavLink> vs. \<Link>?
* 有時候你會在別的範例看到別人使用 **\<Link>**, 而非 **\<NavLink>**, what's the difference?
* 官方說明:*A special version of the \<Link> that will add styling attributes to the rendered element when it matches the current URL.*
----
### \<Switch>...\</Switch>
* 用來定義這個 App 的所有 routings 如何產生畫面
* 一個 **\<Switch>** 裡面包著多個 **\<Route / >**,而每個 **\<Route / >** 用來指定在 \<NavLink> 所定義的 path 連結, 要用哪一個 React compoment 來產生畫面呢?
* 基本 \<Route> 的語法
```jsx
<Route path="/someDir" component={SomeComponent} / >
```
----
### Putting things together...
```jsx
class Blog extends Component {
render() {
return (
<div> // Define your blog layout
<ul>
<li><NavLink to="/posts">Posts</NavLink></li>
<li><NavLink to="/authors">Authors</NavLink></li>
</ul>
<Switch>
<Route path="/posts" component={Posts} / >
<Route path="/authors" components={Authors} / >
</Switch>
</div>
)
}
}
```
----
### URL Parameters
* 通常會把文章根據 IDs, 或者是作者根據名字,來安排至不同的 routings, 例如:
* .../posts/12345678
* .../authors/ric
* 但隨著 Blog 的文章會增加、讀者/作者數量也會增加,不可能在 Blog.js 裡頭把這些文章、作者頁面的 routings 全部預先寫死。因此,我們要用 "參數" 來指定 routing 的規則。例如:
```jsx
<Route path="/posts/:id?" component={PostRender} / >
```
----
### \<Route path="/posts/:id?" component={PostRender} / >
* 當你定義這行時,你事實上就定義了指定的 component (i.e. PostRender) 的 **props.match.params** 多了一個 **"id"** 這個 property
* 換句話說,當你連結 ".../posts/3838" 的時候,就等於把 3838 當作參數傳給 **PostRender** 的 **props.match.params.id**, 你可以在 PostRender 裡頭根據 id 去處理拿到文章的邏輯。
----
### "exact path" for \<Route>
不過當這兩行同時存在的時候...
```jsx
// 列舉所有文章
<Route path="/posts" component={Posts} / >
// 展示某篇文章
<Route path="/posts/:id?" component={PostRender} / >
```
當網址是 "**.../posts/3838**" 的時候,事實上兩條 routing rules 都會符合,所以照順序,會吐出第一個 match (列舉所有文章),而不是展示 3838 這篇文章
所以,第一條應該要改成 **exact path**:
```jsx
<Route exact path="/posts" component={Posts} / >
```
----
### URL Redirect
* 用途:將某個 path ".../pathA" redirect 到另一個 path ".../pathB"
```jsx
<Redirect from="pathA" to="pathB" / >
```
---
### Let's look at a simple yet complete example!
Download ["react-router-boilerplate.tgz"](https://github.com/ric2k1/ric2k1.github.io/blob/master/W8_0410/react-router-boilerplate.tgz) from Github!
----
### Install and Run!
* tar zxvf react-router-boilerplate.tgz
* cd react-router-boilerplate
* npm install
* (If needed) npm audit fix [--force]
* npm start
----
### Code tree (under "src")
```
* index.js // to mount "root" DOM node
* App.js // Define Routing root '/'
* containers/
* Blog/
* Blog.js // Define main page and routing rules
* Posts/
* Posts.js // List all posts (Posts module)
* PostRender.js // Define how to generate posts
* components/
* Post
* Post.js // Define Post module
```
----
### Posts.js (列舉文章列表)
* Note: 理論上文章等資料都應該是從後台 (backend server) 過來,但我們現在還沒有(教)後台,所以我們會把資料寫死在 JS 檔案裡
```jsx
export default class Posts extends Component {
render() {
const postIDs = ["1", "3", "5", "7", "9"];
const lists = postIDs.map((i, index) => (
<li key={index}>
<NavLink to={"/posts/" + i}>Posts #{i}</NavLink>
</li>));
return (
<div>
<h3>Click to view article ---</h3> {lists}
</div>);
}
}
```
----
### **\<li key={index}>** // See p3.3
* 如果把 "key={index}" 拿掉,你會看到這樣的 message: *Warning: Each child in a list should have a unique "key" prop.*
* 原則上如果文章在後台管理,你可以 assign 每一篇文章一個 unique ID, 然後就可以利用這個 ID 來當 \<li> 的 unique keys
* 所以我們在這邊先使用 Array 的 index 來當 key
----
### \<NavLink to={"/posts/" + i}>
### Posts #{i}</NavLink>
* 用來指定當點選某篇文章連結的時候,會 route 到當篇文章的網址 (defined in "Blog.js")
----
### PostRender.js (定義如何產生 posts)
```jsx
export default class PostRender extends Component {
render() {
const postIDs = ["1", "3", "5", "7", "9"];
const { id } = this.props.match.params;
return id && postIDs.includes(id) ? (
<Post id={id} />
) : (
<div>
<h3>Error: Post #{id} NOT FOUND</h3>
</div>
);
}
}
```
* Question: Posts 與 PostRender 可否合在一個 class?
----
* const { id } = this.props.match.params;
=> This is ["Destructuring Assignment"](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment), which is equivalent to:
* const id = this.props.match.params.id;
* You can also do something like:
const { a: b } = obj.someProp,
which is equivalent to:
* const b = obj.someProp.a;
---
### Practice06:Create Routing for your blog page
* Based on "react-router-boilerplate". Re-do your Practice03, "A Static Blog Page in React.js". Make it a **single-page application with React Routing**.
* Since it is still serverless, hard-core the contents and list of articles, etc, in the files.
* For those who want to have a fake database on front-end, you can try ["localStorage"](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). Or refer to this one: [react-router-blogs-example.tgz](https://github.com/ric2k1/ric2k1.github.io/blob/master/W8_0410/react-router-blogs-example.tgz)
---
## That's it!
(You are recommended to go through this [official React Router Tutorial](https://reacttraining.com/react-router/web/guides/quick-start))
{"metaMigratedAt":"2023-06-14T20:58:38.577Z","metaMigratedFrom":"YAML","title":"Advanced Topics on React.js and React Router (04/10)","breaks":true,"slideOptions":"{\"theme\":\"beige\",\"transition\":\"fade\",\"slidenumber\":true}","contributors":"[{\"id\":\"752a44cb-2596-4186-8de2-038ab32eec6b\",\"add\":24251,\"del\":3667},{\"id\":\"3548892a-0c71-4f2e-92b5-26f7c7ea6e8e\",\"add\":20656,\"del\":20655},{\"id\":\"c20a8349-5a60-40ce-8b76-b98cc67ca5d6\",\"add\":0,\"del\":1},{\"id\":\"1616274c-795e-4491-b5fb-f94b3a9a2335\",\"add\":36,\"del\":0}]"}