diff --git a/react/hooks-api-usage/readme.md b/react/hooks-api-usage/readme.md
new file mode 100644
index 0000000..bd3e84c
--- /dev/null
+++ b/react/hooks-api-usage/readme.md
@@ -0,0 +1,10 @@
+# Hooks API - Learning By Doing
+
+## 教材
+
+- [Hooks API 索引](https://zh-hans.reactjs.org/docs/hooks-reference.html)
+- [React Hooks 学习指南](https://fe.rualc.com/note/react-hooks.html)
+
+## Local Dev
+
+`parcel ./useContext/index.html`
diff --git a/react/hooks-api-usage/useCallback/index.html b/react/hooks-api-usage/useCallback/index.html
new file mode 100644
index 0000000..acb8681
--- /dev/null
+++ b/react/hooks-api-usage/useCallback/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
diff --git a/react/hooks-api-usage/useCallback/script.tsx b/react/hooks-api-usage/useCallback/script.tsx
new file mode 100644
index 0000000..66244c2
--- /dev/null
+++ b/react/hooks-api-usage/useCallback/script.tsx
@@ -0,0 +1,45 @@
+import * as React from 'react';
+import { useCallback } from 'react';
+import ReactDOM from 'react-dom';
+
+// * ------------------------------------------------
+
+let ticker = 0;
+let updateFlag = 0;
+
+const sameRandomFunctionFactory = () => {
+ const Rand = Math.random();
+ return () => Rand;
+};
+
+// * ------------------------------------------------
+
+const App = ({ updateFlag }) => {
+ const now = Date.now();
+
+ const cb = useCallback(sameRandomFunctionFactory(), [updateFlag]);
+ const result = cb();
+
+ return (
+
+
+ timer:
+
+ {now}
+
+
+ result of useCallback: ('updateFlag' props changes by 2 secs)
+
+ {result}
+
+
+ );
+};
+
+// * ------------------------------------------------
+
+setInterval(() => {
+ ticker += 1 / 2;
+ updateFlag = Math.round(ticker);
+ ReactDOM.render(, document.querySelector('#app'));
+}, 1000);
diff --git a/react/hooks-api-usage/useContext/index.html b/react/hooks-api-usage/useContext/index.html
new file mode 100644
index 0000000..acb8681
--- /dev/null
+++ b/react/hooks-api-usage/useContext/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
diff --git a/react/hooks-api-usage/useContext/script.tsx b/react/hooks-api-usage/useContext/script.tsx
new file mode 100644
index 0000000..9ab62c5
--- /dev/null
+++ b/react/hooks-api-usage/useContext/script.tsx
@@ -0,0 +1,42 @@
+import * as React from 'react';
+import { createContext, useState, useContext } from 'react';
+import ReactDOM from 'react-dom';
+
+const genColor = () => `hsla(${Math.random() * 360}, 70%, 50%)`;
+
+// * ------------------------------------------------
+
+type MyContext = { color: string; changer: (any) => void };
+
+const ThemeContext = createContext(null);
+
+// * --------------------------------
+
+const Comp = () => {
+ const { color, changer } = useContext(ThemeContext);
+
+ return (
+
+ );
+};
+
+// * --------------------------------
+
+const App = () => {
+ const [state, setState] = useState({ color: 'green' });
+
+ const { color } = state;
+ const changer = () => setState({ color: genColor() });
+
+ return (
+
+
+
+ );
+};
+
+// * ------------------------------------------------
+
+ReactDOM.render(, document.querySelector('#app'));
diff --git a/react/hooks-api-usage/useEffect/index.html b/react/hooks-api-usage/useEffect/index.html
new file mode 100644
index 0000000..acb8681
--- /dev/null
+++ b/react/hooks-api-usage/useEffect/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
diff --git a/react/hooks-api-usage/useEffect/script.tsx b/react/hooks-api-usage/useEffect/script.tsx
new file mode 100644
index 0000000..eeb7ed2
--- /dev/null
+++ b/react/hooks-api-usage/useEffect/script.tsx
@@ -0,0 +1,26 @@
+import * as React from 'react';
+import { useEffect, useState } from 'react';
+import ReactDOM from 'react-dom';
+
+const App = () => {
+ const [count, setCount] = useState(0);
+
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ console.log(`You clicked ${count} times`);
+ }, 1000);
+ return () => {
+ clearTimeout(timer);
+ console.log(`cancel timer`);
+ };
+ });
+
+ return (
+
+
You clicked {count} times
+
+
+ );
+};
+
+ReactDOM.render(, document.querySelector('#app'));
diff --git a/react/hooks-api-usage/useImperativeHandle/index.html b/react/hooks-api-usage/useImperativeHandle/index.html
new file mode 100644
index 0000000..acb8681
--- /dev/null
+++ b/react/hooks-api-usage/useImperativeHandle/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
diff --git a/react/hooks-api-usage/useImperativeHandle/script.tsx b/react/hooks-api-usage/useImperativeHandle/script.tsx
new file mode 100644
index 0000000..b18d51c
--- /dev/null
+++ b/react/hooks-api-usage/useImperativeHandle/script.tsx
@@ -0,0 +1,33 @@
+import * as React from 'react';
+import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
+import ReactDOM from 'react-dom';
+
+// * ------------------------------------------------
+
+const ChildInput = forwardRef((props, ref) => {
+ const realRef = useRef(null);
+
+ useImperativeHandle(ref, () => realRef.current);
+
+ return ;
+});
+
+// * --------------------------------
+
+const App = () => {
+ const inputRef = useRef(null);
+
+ useEffect(() => {
+ inputRef.current.focus();
+ }, []);
+
+ return (
+
+
+
+ );
+};
+
+// * ------------------------------------------------
+
+ReactDOM.render(, document.querySelector('#app'));
diff --git a/react/hooks-api-usage/useLayoutEffect/index.html b/react/hooks-api-usage/useLayoutEffect/index.html
new file mode 100644
index 0000000..acb8681
--- /dev/null
+++ b/react/hooks-api-usage/useLayoutEffect/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
diff --git a/react/hooks-api-usage/useLayoutEffect/script.tsx b/react/hooks-api-usage/useLayoutEffect/script.tsx
new file mode 100644
index 0000000..77cc0e8
--- /dev/null
+++ b/react/hooks-api-usage/useLayoutEffect/script.tsx
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import { useLayoutEffect, useRef, useState } from 'react';
+import ReactDOM from 'react-dom';
+
+const App = () => {
+ const [height, setHeight] = useState(0);
+ const refer = useRef(null);
+
+ useLayoutEffect(() => {
+ const $dom = refer.current;
+ const domHeight = $dom.getBoundingClientRect().height;
+ if (height !== domHeight) setHeight(domHeight);
+ });
+
+ return (
+
+
hello
+ {height}
+
+ );
+};
+
+ReactDOM.render(, document.querySelector('#app'));
diff --git a/react/hooks-api-usage/useMemo/index.html b/react/hooks-api-usage/useMemo/index.html
new file mode 100644
index 0000000..acb8681
--- /dev/null
+++ b/react/hooks-api-usage/useMemo/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
diff --git a/react/hooks-api-usage/useMemo/script.tsx b/react/hooks-api-usage/useMemo/script.tsx
new file mode 100644
index 0000000..c62064d
--- /dev/null
+++ b/react/hooks-api-usage/useMemo/script.tsx
@@ -0,0 +1,56 @@
+import * as React from 'react';
+import { useMemo } from 'react';
+import ReactDOM from 'react-dom';
+
+import { memoizeWith } from 'ramda';
+import { memoize } from 'lodash-es';
+
+// * ------------------------------------------------
+
+let ticker = 0;
+let updateFlag = 0;
+
+const getRandom = () => Math.random();
+
+// * try different tools, but react useMemo only keep the previous deps to compare
+const randamMemo = memoizeWith(() => updateFlag, getRandom);
+const lodashMemo = memoize(getRandom, () => updateFlag);
+
+// * ------------------------------------------------
+
+const App = ({ updateFlag }) => {
+ const now = Date.now();
+
+ let result;
+
+ {
+ result = useMemo(getRandom, [updateFlag]);
+
+ // * try different tools, try declare here, they would fail
+ // result = randamMemo();
+ // result = lodashMemo();
+ }
+
+ return (
+
+
+ timer:
+
+ {now}
+
+
+ result of useMemo: ('updateFlag' props changes by 2 secs)
+
+ {result}
+
+
+ );
+};
+
+// * ------------------------------------------------
+
+setInterval(() => {
+ ticker += 1 / 2;
+ updateFlag = Math.round(ticker);
+ ReactDOM.render(, document.querySelector('#app'));
+}, 1000);
diff --git a/react/hooks-api-usage/useReducer/index.html b/react/hooks-api-usage/useReducer/index.html
new file mode 100644
index 0000000..acb8681
--- /dev/null
+++ b/react/hooks-api-usage/useReducer/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
diff --git a/react/hooks-api-usage/useReducer/script.tsx b/react/hooks-api-usage/useReducer/script.tsx
new file mode 100644
index 0000000..7917466
--- /dev/null
+++ b/react/hooks-api-usage/useReducer/script.tsx
@@ -0,0 +1,27 @@
+import * as React from 'react';
+import { useReducer } from 'react';
+import ReactDOM from 'react-dom';
+
+type MyReducer = React.Reducer<{ value: number }, { type: string; payload?: number }>;
+
+const reducer: MyReducer = (state, action) => {
+ const { value } = state;
+ const { type, payload = 1 } = action;
+ const nextValue = type === 'plus' ? value + payload : type === 'minus' ? value - payload : value;
+ return { value: nextValue };
+};
+
+const App = () => {
+ const [state, dispatch] = useReducer(reducer, { value: 0 });
+
+ return (
+ <>
+ Count by useReducer: {state.value}
+
+
+
+ >
+ );
+};
+
+ReactDOM.render(, document.querySelector('#app'));
diff --git a/react/hooks-api-usage/useRef/index.html b/react/hooks-api-usage/useRef/index.html
new file mode 100644
index 0000000..acb8681
--- /dev/null
+++ b/react/hooks-api-usage/useRef/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
diff --git a/react/hooks-api-usage/useRef/script.tsx b/react/hooks-api-usage/useRef/script.tsx
new file mode 100644
index 0000000..e5f5ecb
--- /dev/null
+++ b/react/hooks-api-usage/useRef/script.tsx
@@ -0,0 +1,19 @@
+import * as React from 'react';
+import { useRef } from 'react';
+import ReactDOM from 'react-dom';
+
+const App = () => {
+ const inputer = useRef(null);
+
+ const logInputValue = () => console.log('input value:', inputer.current.value);
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
+
+ReactDOM.render(, document.querySelector('#app'));
diff --git a/react/hooks-api-usage/useState/index.html b/react/hooks-api-usage/useState/index.html
new file mode 100644
index 0000000..acb8681
--- /dev/null
+++ b/react/hooks-api-usage/useState/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
diff --git a/react/hooks-api-usage/useState/script.tsx b/react/hooks-api-usage/useState/script.tsx
new file mode 100644
index 0000000..ac725b6
--- /dev/null
+++ b/react/hooks-api-usage/useState/script.tsx
@@ -0,0 +1,21 @@
+import * as React from 'react';
+import { useState } from 'react';
+import ReactDOM from 'react-dom';
+
+const App = () => {
+ const [state, setState] = useState({ value: 0 });
+
+ const plusFn = () => setState({ value: state.value + 5 });
+ const minusFn = () => setState((state) => ({ value: state.value - 3 }));
+
+ return (
+ <>
+ Count by useState: {state.value}
+
+
+
+ >
+ );
+};
+
+ReactDOM.render(, document.querySelector('#app'));
diff --git a/react/hooks-example/readme.md b/react/hooks-example/readme.md
new file mode 100644
index 0000000..0fac331
--- /dev/null
+++ b/react/hooks-example/readme.md
@@ -0,0 +1,12 @@
+# Hooks Example - Learning By Doing
+
+## 教材
+
+- [Hooks API 索引](https://zh-hans.reactjs.org/docs/hooks-reference.html)
+- [Hooks API usage](../hooks-api-usage/)
+- [示例 - React](https://zh-hans.reactjs.org/community/examples.html)
+- [React Hooks 学习指南](https://fe.rualc.com/note/react-hooks.html)
+
+## Local Dev
+
+`parcel ./temp-transfer/index.html`
diff --git a/react/hooks-example/temp-transfer/index.html b/react/hooks-example/temp-transfer/index.html
new file mode 100644
index 0000000..4cca6dc
--- /dev/null
+++ b/react/hooks-example/temp-transfer/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
diff --git a/react/hooks-example/temp-transfer/script.tsx b/react/hooks-example/temp-transfer/script.tsx
new file mode 100644
index 0000000..bac5d5d
--- /dev/null
+++ b/react/hooks-example/temp-transfer/script.tsx
@@ -0,0 +1,75 @@
+// * https://zh-hans.reactjs.org/docs/lifting-state-up.html
+
+import * as React from 'react';
+import { useState } from 'react';
+import ReactDOM from 'react-dom';
+
+// * ----------------------------------------------------------------
+
+const toC = (fahrenheit) => ((fahrenheit - 32) * 5) / 9;
+
+const toF = (celsius) => (celsius * 9) / 5 + 32;
+
+const transTemp = (temperature, convert) =>
+ Number.isNaN(parseFloat(temperature))
+ ? ''
+ : (Math.round(convert(parseFloat(temperature)) * 1000) / 1000).toString();
+
+const scaleNames = {
+ c: 'Celsius',
+ f: 'Fahrenheit',
+};
+
+// * ----------------------------------------------------------------
+
+const BoilingVerdict = (props) => (
+
+ The water would {props.celsius >= 100 ? '' : 'not'} boil at {props.celsius} Celsius
+
+);
+
+const TemperatureInput = ({ temperature, scale, onTemperatureChange }) => {
+ return (
+
+ );
+};
+
+// * --------------------------------
+
+const Calculator = () => {
+ const [state, setState] = useState({ temperature: '', scale: 'c' });
+
+ const { scale, temperature } = state;
+
+ const celsius = scale === 'c' ? temperature : transTemp(temperature, toC);
+ const fahrenheit = scale === 'f' ? temperature : transTemp(temperature, toF);
+
+ const handler = (scale) => (e) => setState({ scale, temperature: e.target.value });
+ const handleCelsiusChange = handler('c');
+ const handleFahrenheitChange = handler('f');
+
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+const App = () => ;
+
+ReactDOM.render(, document.querySelector('#app'));
diff --git a/react/hooks-example/unit-converter/readme.md b/react/hooks-example/unit-converter/readme.md
new file mode 100644
index 0000000..84314e1
--- /dev/null
+++ b/react/hooks-example/unit-converter/readme.md
@@ -0,0 +1,13 @@
+# Unit Converter
+
+## Example From
+
+https://github.com/KarthikeyanRanasthala/react-unit-converter
+
+## Hooks Used
+
+- useState
+
+## Dev
+
+`parcel ./src/index.html`
diff --git a/react/hooks-example/unit-converter/src/components/app.jsx b/react/hooks-example/unit-converter/src/components/app.jsx
new file mode 100644
index 0000000..1a06248
--- /dev/null
+++ b/react/hooks-example/unit-converter/src/components/app.jsx
@@ -0,0 +1,119 @@
+import React, { useState } from 'react';
+
+import convert from 'convert-units';
+
+import Container from '@material-ui/core/Container';
+import Grid from '@material-ui/core/Grid';
+
+import { Selector } from './selector';
+import { InputField } from './input';
+
+// * ------------------------------------------------
+
+const updateCalc = (name, { leftType, rightType, leftValue, rightValue }) => {
+ if ([leftType, rightType, leftValue, rightValue].filter((e) => !e).length > 1) return {};
+
+ const fromLeft = name === 'leftValue' || name === 'rightType';
+ return fromLeft
+ ? {
+ rightValue: convert(leftValue)
+ .from(leftType)
+ .to(rightType),
+ }
+ : {
+ leftValue: convert(rightValue)
+ .from(rightType)
+ .to(leftType),
+ };
+};
+
+const handleMesureFactory = (setState) => (event) => {
+ const { name, value } = event.target;
+
+ setState((state) => ({
+ ...state,
+ ...emptyState,
+ [name]: value,
+ }));
+};
+
+const emptyState = {
+ Measurement: '',
+ leftType: '',
+ rightType: '',
+ leftValue: '',
+ rightValue: '',
+};
+
+const handleSelectorFactory = (setState) => (event) => {
+ const { name, value } = event.target;
+
+ setState((state) => {
+ const nextState = { ...state, [name]: value };
+ const calcPatch = updateCalc(name, nextState);
+ return { ...nextState, ...calcPatch };
+ });
+};
+
+// * ------------------------------------------------
+
+export const App = () => {
+ const [state, setState] = useState(emptyState);
+
+ const handleMesure = handleMesureFactory(setState);
+ const handleSelector = handleSelectorFactory(setState);
+
+ return (
+
+
+
+ {state.Measurement ? (
+ <>
+
+
+ >
+ ) : (
+ <>>
+ )}
+ {state.leftType && state.rightType ? (
+ <>
+
+
+ >
+ ) : (
+ <>>
+ )}
+
+
+ );
+};
diff --git a/react/hooks-example/unit-converter/src/components/input.jsx b/react/hooks-example/unit-converter/src/components/input.jsx
new file mode 100644
index 0000000..768976f
--- /dev/null
+++ b/react/hooks-example/unit-converter/src/components/input.jsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import TextField from '@material-ui/core/TextField';
+import InputAdornment from '@material-ui/core/InputAdornment';
+import Grid from '@material-ui/core/Grid';
+
+export const InputField = ({ label, value, name, onChange }) => {
+ return (
+
+ {label},
+ }}
+ />
+
+ );
+};
diff --git a/react/hooks-example/unit-converter/src/components/selector.jsx b/react/hooks-example/unit-converter/src/components/selector.jsx
new file mode 100644
index 0000000..16b9468
--- /dev/null
+++ b/react/hooks-example/unit-converter/src/components/selector.jsx
@@ -0,0 +1,32 @@
+import React from 'react';
+
+import Grid from '@material-ui/core/Grid';
+import FormControl from '@material-ui/core/FormControl';
+import InputLabel from '@material-ui/core/InputLabel';
+import Select from '@material-ui/core/Select';
+import MenuItem from '@material-ui/core/MenuItem';
+
+export const Selector = ({ list = [], value, mdWidth, label, name, onChange }) => {
+ return (
+
+
+ {label}
+
+
+
+ );
+};
diff --git a/react/hooks-example/unit-converter/src/index.html b/react/hooks-example/unit-converter/src/index.html
new file mode 100644
index 0000000..5ef9983
--- /dev/null
+++ b/react/hooks-example/unit-converter/src/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
diff --git a/react/hooks-example/unit-converter/src/script.js b/react/hooks-example/unit-converter/src/script.js
new file mode 100644
index 0000000..6647c0f
--- /dev/null
+++ b/react/hooks-example/unit-converter/src/script.js
@@ -0,0 +1,6 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { App } from './components/app';
+
+ReactDOM.render(, document.querySelector('#app'));
+