├── .gitignore ├── LICENSE ├── README.md ├── chapter01 ├── 01_usecount.js ├── 02_globalstate.js ├── 03_usestate.js ├── 04_usereducer.js ├── 05_usestate_with_usereducer.js ├── 06_usereducer_with_usestate.js ├── 07_usereducer_init.js └── 08_usereducer_inline.js ├── chapter02 ├── 01_localstates.js ├── 02_lift_state_up.js ├── 03_lift_content_up.js └── 04_globalstate.js ├── chapter03 ├── 01_using-usestate-without-usecontext │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 02_using-usecontext-with-static-value │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 03_using-usestate-with-usecontext │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 04_how-context-propagation-works │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 05_pitfall-when-using-context-for-object │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 06_creating-small-state-pieces │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 07_creating-one-state-with-userreducer-and-propagate-with-multiple-contexts │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 08_creating-custom-hook-and-provider-component │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 09_factory-pattern-with-custom-hook │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock └── 10_avoiding-provider-nesting-reduceright │ ├── package.json │ ├── public │ └── index.html │ ├── src │ ├── App.tsx │ ├── index.tsx │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── chapter04 ├── 01_naive_solution_to_module_state │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 02_use_store_with_subscription │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 03_use_store_with_selector │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock └── 04_use_subscription_with_store │ ├── package.json │ ├── public │ └── index.html │ ├── src │ ├── App.tsx │ ├── index.tsx │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── chapter05 └── 01_combine_context_and_subscription │ ├── package.json │ ├── public │ └── index.html │ ├── src │ ├── App.tsx │ ├── index.tsx │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── chapter07 ├── 01_counter_example │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock └── 02_todo_example │ ├── package.json │ ├── public │ └── index.html │ ├── src │ ├── App.tsx │ ├── index.tsx │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── chapter08 ├── 01_comparison │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── AppWithContext.tsx │ │ ├── AppWithJotai.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 02_counter │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 03_provider │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 04_todo_app_single_atom │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock └── 05_todo_app_atoms_in_atom │ ├── package.json │ ├── public │ └── index.html │ ├── src │ ├── App.tsx │ ├── index.tsx │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── chapter09 ├── 01_counter │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 02_todo_app │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock └── 03_another_todo_app │ ├── package.json │ ├── public │ └── index.html │ ├── src │ ├── App.tsx │ ├── index.tsx │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── chapter10 ├── 01_bare_context │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 02_with_usestate │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 03_with_usereducer │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock └── 04_with_reactredux │ ├── package.json │ ├── public │ └── index.html │ ├── src │ ├── App.tsx │ ├── index.tsx │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── chapter11 ├── 01_redux_counter │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── app │ │ │ └── store.ts │ │ ├── features │ │ │ └── counter │ │ │ │ ├── Counter.tsx │ │ │ │ └── counterSlice.ts │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 02_zustand_counter │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── Counter.tsx │ │ ├── index.tsx │ │ ├── react-app-env.d.ts │ │ └── store.ts │ ├── tsconfig.json │ └── yarn.lock ├── 03_recoil_charcounter │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 04_jotai_charcounter │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── 05_mobx_timer │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock └── 06_valtio_timer │ ├── package.json │ ├── public │ └── index.html │ ├── src │ ├── App.tsx │ ├── index.tsx │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock └── cover.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | node_modules 4 | build 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![cover](cover.jpg) 2 | 3 | # 리액트 훅을 활용한 마이크로 상태 관리 4 | ### 리액트 상태 관리의 기본 개념부터 동작 원리와 문제 해결, 렌더링 최적화 기법까지 5 | 6 | - **다이시 카토** 지음 | **이선협, 김지은** 옮김 7 | - ISBN: 9791158394899 8 | - 판형: 175\*235\*14mm 9 | - 27,000원 | 2024년 2월 20일 발행 | 260쪽 10 | - [책 홈페이지](https://wikibook.co.kr/msmrh/) 11 | - [도서 미리보기](http://www.yes24.com/Product/Viewer/Preview/124899726) 12 | - [도서 관련 문의](https://wikibook.co.kr/support/contact/) 13 | 14 | --- 15 | 16 | 이 책에서는 다양한 상태 관리 방법과 유명한 상태 관리 라이브러리인 Zustand, Jotai, Valtio, React Tracked의 사용법을 소개한다. 또한 실무에서 유용하게 활용할 수 있는 여러 사용 사례에 대한 패턴과 리렌더링 최적화에 대한 내용을 다룬다. 17 | 18 | 이 책을 처음부터 끝까지 정독하면 리액트에서 상태를 관리하는 방법과 원리를 비롯해 애플리케이션 요구사항에 적합한 상태 관리 라이브러리를 선택할 수 있을 것이다. 19 | 20 | **★ 이 책에서 다루는 내용 ★** 21 | 22 | - 마이크로 상태 관리의 개념과 구현 23 | - 지역 상태와 전역 상태의 개념과 구현 24 | - 리액트 컨텍스트를 통한 전역 상태 관리 25 | - 모듈 상태를 통한 전역 상태 관리 26 | - 리렌더링 최적화 27 | - Zustand, Jotai, Valtio, React Tracked의 사용법과 동작 원리 28 | - 여러 상태 관리 라이브러리의 장단점 비교 29 | - 요구사항에 적합한 라이브러리 선택 방법 30 | 31 | --- 32 | 33 | ## 구입처 34 | 35 | - [예스24](https://www.yes24.com/Product/Goods/124899726) 36 | - [교보문고](https://product.kyobobook.co.kr/detail/S000212233308) 37 | - [인터파크](https://book.interpark.com/product/BookDisplay.do?_method=detail&sc.prdNo=356868972) 38 | - [알라딘](https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=333599370) -------------------------------------------------------------------------------- /chapter01/01_usecount.js: -------------------------------------------------------------------------------- 1 | const Component = () => { 2 | const [count, setCount] = useState(0); 3 | return ( 4 |
5 | {count} 6 | 7 |
8 | ); 9 | }; 10 | 11 | const useCount = () => { 12 | const [count, setCount] = useState(0); 13 | return [count, setCount]; 14 | }; 15 | 16 | const Component = () => { 17 | const [count, setCount] = useCount(); 18 | return ( 19 |
20 | {count} 21 | 22 |
23 | ); 24 | }; 25 | 26 | const useCount = () => { 27 | const [count, setCount] = useState(0); 28 | useEffect(() => { 29 | console.log('count is changed to', count); 30 | }, [count]); 31 | return [count, setCount]; 32 | }; 33 | 34 | const useCount = () => { 35 | const [count, setCount] = useState(0); 36 | const inc = () => setCount((c) => c + 1); 37 | return [count, inc]; 38 | }; 39 | -------------------------------------------------------------------------------- /chapter01/02_globalstate.js: -------------------------------------------------------------------------------- 1 | const Component = () => { 2 | const [state, setState] = useState(); 3 | return ( 4 |
5 | {JSON.stringify(state)} 6 | 7 |
8 | ); 9 | }; 10 | 11 | const Child = ({ state, setState }) => { 12 | const setFoo = () => setState( 13 | (prev) => ({ ...prev, foo: ‘foo’ }) 14 | ); 15 | return ( 16 |
17 | {JSON.stringify(state)} 18 | 19 |
20 | ); 21 | }; 22 | 23 | const Component1 = () => { 24 | const [state, setState] = useGlobalState(); 25 | return ( 26 |
27 | {JSON.stringify(state)} 28 |
29 | ); 30 | }; 31 | 32 | const Component2 = () => { 33 | const [state, setState] = useGlobalState(); 34 | return ( 35 |
36 | {JSON.stringify(state)} 37 |
38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /chapter01/03_usestate.js: -------------------------------------------------------------------------------- 1 | const Component = () => { 2 | const [count, setCount] = useState(0); 3 | return ( 4 |
5 | {count} 6 | 7 |
8 | ); 9 | }; 10 | 11 | const Component = () => { 12 | const [state, setState] = useState({ count: 0 }); 13 | return ( 14 |
15 | {state.count} 16 | 19 |
20 | ); 21 | }; 22 | 23 | const Component = () => { 24 | const [state, setState] = useState({ count: 0 }); 25 | return ( 26 |
27 | {state.count} 28 | 33 |
34 | ); 35 | }; 36 | 37 | const Component = () => { 38 | const [count, setCount] = useState(0); 39 | return ( 40 |
41 | {count} 42 | 45 |
46 | ); 47 | }; 48 | 49 | const Component = () => { 50 | const [count, setCount] = useState(0); 51 | return ( 52 |
53 | {count} 54 | 57 |
58 | ); 59 | }; 60 | 61 | const Component = () => { 62 | const [count, setCount] = useState(0); 63 | useEffect(() => { 64 | const id = setInterval(() => setCount((c) => c + 1), 1000); 65 | return () => clearInterval(id); 66 | }, []); 67 | return ( 68 |
69 | {count} 70 | 76 |
77 | ); 78 | }; 79 | 80 | const init = () => 0; 81 | 82 | const Component = () => { 83 | const [count, setCount] = useState(init); 84 | return ( 85 |
86 | {count} 87 | 90 |
91 | ); 92 | }; 93 | -------------------------------------------------------------------------------- /chapter01/04_usereducer.js: -------------------------------------------------------------------------------- 1 | const reducer = (state, action) => { 2 | switch (action.type) { 3 | case 'INCREMENT': 4 | return { ...state, count: state.count + 1 }; 5 | case 'SET_TEXT': 6 | return { ...state, text: action.text }; 7 | default: 8 | throw new Error('unknown action type'); 9 | } 10 | }; 11 | 12 | const Component = () => { 13 | const [state, dispatch] = useReducer( 14 | reducer, 15 | { count: 0, text: 'hi' }, 16 | ); 17 | return ( 18 |
19 | {state.count} 20 | 25 | 28 | dispatch({ type: 'SET_TEXT', text: e.target.value })} 29 | /> 30 |
31 | ); 32 | }; 33 | 34 | const reducer = (state, action) => { 35 | switch (action.type) { 36 | case 'INCREMENT': 37 | return { ...state, count: state.count + 1 }; 38 | case 'SET_TEXT': 39 | if (!action.text) { 40 | // bail out 41 | return state 42 | } 43 | return { ...state, text: action.text }; 44 | default: 45 | throw new Error('unknown action type'); 46 | } 47 | }; 48 | 49 | const reducer = (count, delta) => { 50 | if (delta < 0) { 51 | throw new Error('delta cannot be negative'); 52 | } 53 | if (delta > 10) { 54 | // too big, just ignore 55 | return count 56 | } 57 | if (count < 100) { 58 | // add bonus 59 | return count + delta + 10 60 | } 61 | return count + delta 62 | } 63 | 64 | const init = (count) => ({ count, text: 'hi' }); 65 | 66 | const reducer = (state, action) => { 67 | switch (action.type) { 68 | case 'INCREMENT': 69 | return { ...state, count: state.count + 1 }; 70 | case 'SET_TEXT': 71 | return { ...state, text: action.text }; 72 | default: 73 | throw new Error('unknown action type'); 74 | } 75 | }; 76 | 77 | const Component = () => { 78 | const [state, dispatch] = useReducer(reducer, 0, init); 79 | return ( 80 |
81 | {state.count} 82 | 87 | 90 | dispatch({ type: 'SET_TEXT', text: e.target.value })} 91 | /> 92 |
93 | ); 94 | }; 95 | -------------------------------------------------------------------------------- /chapter01/05_usestate_with_usereducer.js: -------------------------------------------------------------------------------- 1 | const useState = (initialState) => { 2 | const [state, dispatch] = useReducer( 3 | (prev, action) => 4 | typeof action === 'function' ? action(prev) : action, 5 | initialState, 6 | ); 7 | return [state, dispatch]; 8 | }; 9 | 10 | const reducer = (prev, action) => 11 | typeof action === 'function' ? action(prev): action; 12 | 13 | const useState = (initialState) => 14 | useReducer(reducer, initialState); 15 | -------------------------------------------------------------------------------- /chapter01/06_usereducer_with_usestate.js: -------------------------------------------------------------------------------- 1 | const useReducer = (reducer, initialState) => { 2 | const [state, setState] = useState(initialState); 3 | const dispatch = (action) => 4 | setState(prev => reducer(prev, action)); 5 | return [state, dispatch]; 6 | }; 7 | 8 | const useReducer = (reducer, initialArg, init) => { 9 | const [state, setState] = useState( 10 | init ? () => init(initialArg) : initialArg, 11 | ); 12 | const dispatch = useCallback( 13 | (action) => setState(prev => reducer(prev, action)), 14 | [reducer], 15 | ); 16 | return [state, dispatch]; 17 | }; 18 | -------------------------------------------------------------------------------- /chapter01/07_usereducer_init.js: -------------------------------------------------------------------------------- 1 | const init = (count) => ({ count }) 2 | 3 | const reducer = (prev, delta) => ({ ...prev, count: prev.count + delta }) 4 | 5 | const ComponentWithUseReducer = ({ initialCount }) => { 6 | const [state, dispatch] = useReducer( 7 | reducer, 8 | initialCount, 9 | init, 10 | ); 11 | return ( 12 |
13 | {state.count} 14 | 15 |
16 | ); 17 | }; 18 | 19 | const ComponentWithUseState = ({ initialCount }) => { 20 | const [state, setState] = useState(() => init(initialCount)); 21 | const dispatch = (delta) => 22 | setState((prev) => reducer(prev, delta)); 23 | return ( 24 |
25 | {state.count} 26 | 27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /chapter01/08_usereducer_inline.js: -------------------------------------------------------------------------------- 1 | const useScore = (bonus) => 2 | useReducer((prev, delta) => prev + delta + bonus, 0) 3 | -------------------------------------------------------------------------------- /chapter02/01_localstates.js: -------------------------------------------------------------------------------- 1 | const addOne = (n) => n + 1; 2 | 3 | let base = 1; 4 | 5 | const addBase = (n) => n + base; 6 | 7 | const createContainer = () => { 8 | let base = 1; 9 | const addBase = (n) => n + base; 10 | const changeBase = (b) => { base = b; }; 11 | return { addBase, changeBase }; 12 | }; 13 | 14 | const { addBase, changeBase } = createContainer(); 15 | 16 | const Component = ({ number }) => { 17 | return
{number}
; 18 | }; 19 | 20 | const AddOne = ({ number }) => { 21 | return
{number + 1}
; 22 | }; 23 | 24 | const AddBase = ({ number }) => { 25 | const [base, changeBase] = useState(1); 26 | return
{number + base}
; 27 | }; 28 | -------------------------------------------------------------------------------- /chapter02/02_lift_state_up.js: -------------------------------------------------------------------------------- 1 | const Component1 = () => { 2 | const [count, setCount] = useState(0); 3 | return ( 4 |
5 | {count} 6 | 9 |
10 | ); 11 | }; 12 | 13 | const Component2 = () => { 14 | const [count, setCount] = useState(0); 15 | return ( 16 |
17 | {count} 18 | 21 |
22 | ); 23 | }; 24 | 25 | const Component1 = ({ count, setCount }) => { 26 | return ( 27 |
28 | {count} 29 | 32 |
33 | ); 34 | }; 35 | 36 | const Component2 = ({ count, setCount }) => { 37 | return ( 38 |
39 | {count} 40 | 43 |
44 | ); 45 | }; 46 | 47 | const Parent = () => { 48 | const [count, setCount] = useState(0); 49 | return ( 50 | <> 51 | 52 | 53 | 54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /chapter02/03_lift_content_up.js: -------------------------------------------------------------------------------- 1 | const AdditionalInfo = () => { 2 | return

Some information

3 | }; 4 | 5 | const Component1 = ({ count, setCount }) => { 6 | return ( 7 |
8 | {count} 9 | 12 | 13 |
14 | ); 15 | }; 16 | 17 | const Parent = () => { 18 | const [count, setCount] = useState(0); 19 | return ( 20 | <> 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | const AdditionalInfo = () => { 28 | return

Some information

29 | }; 30 | 31 | const Component1 = ({ count, setCount, additionalInfo }) => { 32 | return ( 33 |
34 | {count} 35 | 38 | {additionalInfo} 39 |
40 | ); 41 | }; 42 | 43 | const Parent = ({ additionalInfo }) => { 44 | const [count, setCount] = useState(0); 45 | return ( 46 | <> 47 | 52 | 53 | 54 | ); 55 | }; 56 | 57 | const GrandParent = () => { 58 | return } />; 59 | }; 60 | 61 | const AdditionalInfo = () => { 62 | return

Some information

63 | }; 64 | 65 | const Component1 = ({ count, setCount, children }) => { 66 | return ( 67 |
68 | {count} 69 | 72 | {children} 73 |
74 | ); 75 | }; 76 | 77 | const Parent = ({ children }) => { 78 | const [count, setCount] = useState(0); 79 | return ( 80 | <> 81 | 82 | {children} 83 | 84 | 85 | 86 | ); 87 | }; 88 | 89 | const GrandParent = () => { 90 | return ( 91 | 92 | 93 | 94 | ); 95 | }; 96 | -------------------------------------------------------------------------------- /chapter02/04_globalstate.js: -------------------------------------------------------------------------------- 1 | const createContainer = () => { 2 | let base = 1; 3 | const addBase = (n) => n + base; 4 | const changeBase = (b) => { base = b; }; 5 | return { addBase, changeBase }; 6 | }; 7 | 8 | const container1 = createContainer(); 9 | const container2 = createContainer(); 10 | 11 | container1.changeBase(10); 12 | 13 | console.log(container1.addBase(2)); // shows "12" 14 | console.log(container2.addBase(2)); // shows "3" 15 | 16 | const Component1 = ({ count, setCount }) => { 17 | return ( 18 |
19 | {count} 20 | 23 |
24 | ); 25 | }; 26 | 27 | const Parent = ({ count, setCount }) => { 28 | return ( 29 | <> 30 | 31 | 32 | ); 33 | }; 34 | 35 | const GrandParent = ({ count, setCount }) => { 36 | return ( 37 | <> 38 | 39 | 40 | ); 41 | }; 42 | 43 | const Root = () => { 44 | const [count, setCount] = useState(0); 45 | return ( 46 | <> 47 | 48 | 49 | ); 50 | }; 51 | 52 | const Component1 = () => { 53 | // useGlobalCountState is a pseudo hook 54 | const [count, setCount] = useGlobalCountState(); 55 | return ( 56 |
57 | {count} 58 | 61 |
62 | ); 63 | }; 64 | 65 | const Parent = () => { 66 | return ( 67 | <> 68 | 69 | 70 | ); 71 | }; 72 | 73 | const GrandParent = () => { 74 | return ( 75 | <> 76 | 77 | 78 | ); 79 | }; 80 | 81 | const Root = () => { 82 | return ( 83 | <> 84 | 85 | 86 | ); 87 | }; 88 | 89 | const globalState = { 90 | authInfo: { name: 'React' }, 91 | }; 92 | 93 | const Component1 = () => { 94 | // useGlobalState is a pseudo hook 95 | const { authInfo } = useGlobalState(); 96 | return ( 97 |
98 | {authInfo.name} 99 |
100 | ); 101 | }; 102 | -------------------------------------------------------------------------------- /chapter03/01_using-usestate-without-usecontext/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "web-vitals": "^1.0.1" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /chapter03/01_using-usestate-without-usecontext/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter03/01_using-usestate-without-usecontext/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Dispatch, SetStateAction, useState } from "react"; 2 | 3 | const App = () => { 4 | const [count, setCount] = useState(0); 5 | return ; 6 | }; 7 | 8 | const Parent = ({ 9 | count, 10 | setCount, 11 | }: { 12 | count: number; 13 | setCount: Dispatch>; 14 | }) => ( 15 | <> 16 | 17 | 18 | 19 | ); 20 | 21 | const Component1 = ({ 22 | count, 23 | setCount, 24 | }: { 25 | count: number; 26 | setCount: Dispatch>; 27 | }) => ( 28 |
29 | {count} 30 | 31 |
32 | ); 33 | 34 | const Component2 = ({ 35 | count, 36 | setCount, 37 | }: { 38 | count: number; 39 | setCount: Dispatch>; 40 | }) => ( 41 |
42 | {count} 43 | 44 |
45 | ); 46 | 47 | export default App; 48 | -------------------------------------------------------------------------------- /chapter03/01_using-usestate-without-usecontext/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter03/01_using-usestate-without-usecontext/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter03/01_using-usestate-without-usecontext/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter03/02_using-usecontext-with-static-value/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "web-vitals": "^1.0.1" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /chapter03/02_using-usecontext-with-static-value/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter03/02_using-usecontext-with-static-value/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from "react"; 2 | 3 | const ColorContext = createContext('black'); 4 | 5 | const Component = () => { 6 | const color = useContext(ColorContext); 7 | return
Hello {color}
; 8 | }; 9 | 10 | const App = () => ( 11 | <> 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | 28 | export default App; 29 | -------------------------------------------------------------------------------- /chapter03/02_using-usecontext-with-static-value/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter03/02_using-usecontext-with-static-value/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter03/02_using-usecontext-with-static-value/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter03/03_using-usestate-with-usecontext/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "web-vitals": "^1.0.1" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /chapter03/03_using-usestate-with-usecontext/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter03/03_using-usestate-with-usecontext/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { SetStateAction, createContext, useContext, useState } from "react"; 2 | 3 | const CountStateContext = createContext({ count: 0, setCount: (_: SetStateAction) => {} }); 4 | 5 | const App = () => { 6 | const [count, setCount] = useState(0); 7 | return ( 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | const Parent = () => ( 15 | <> 16 | 17 | 18 | 19 | ); 20 | 21 | const Component1 = () => { 22 | const { count, setCount } = useContext(CountStateContext); 23 | return ( 24 |
25 | {count} 26 | 27 |
28 | ); 29 | }; 30 | 31 | const Component2 = () => { 32 | const { count, setCount } = useContext(CountStateContext); 33 | return ( 34 |
35 | {count} 36 | 37 |
38 | ); 39 | }; 40 | 41 | export default App; 42 | -------------------------------------------------------------------------------- /chapter03/03_using-usestate-with-usecontext/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter03/03_using-usestate-with-usecontext/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter03/03_using-usestate-with-usecontext/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter03/04_how-context-propagation-works/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "web-vitals": "^1.0.1" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /chapter03/04_how-context-propagation-works/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter03/04_how-context-propagation-works/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | memo, 3 | createContext, 4 | useContext, 5 | useRef, 6 | useEffect, 7 | useState, 8 | } from "react"; 9 | 10 | const ColorContext = createContext("black"); 11 | 12 | const ColorComponent = () => { 13 | const color = useContext(ColorContext); 14 | const renderCount = useRef(1); 15 | useEffect(() => { 16 | renderCount.current += 1; 17 | }); 18 | return ( 19 |
20 | Hello {color} (renders: {renderCount.current}) 21 |
22 | ); 23 | }; 24 | 25 | const MemoedColorComponent = memo(ColorComponent); 26 | 27 | const DummyComponent = () => { 28 | const renderCount = useRef(1); 29 | useEffect(() => { 30 | renderCount.current += 1; 31 | }); 32 | return
Dummy (renders: {renderCount.current})
; 33 | }; 34 | 35 | const MemoedDummyComponent = memo(DummyComponent); 36 | 37 | const Parent = () => ( 38 |
    39 |
  • 40 | 41 |
  • 42 |
  • 43 | 44 |
  • 45 |
  • 46 | 47 |
  • 48 |
  • 49 | 50 |
  • 51 |
52 | ); 53 | 54 | const App = () => { 55 | const [color, setColor] = useState("red"); 56 | return ( 57 | 58 | setColor(e.target.value)} /> 59 | 60 | 61 | ); 62 | }; 63 | 64 | export default App; 65 | -------------------------------------------------------------------------------- /chapter03/04_how-context-propagation-works/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter03/04_how-context-propagation-works/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter03/04_how-context-propagation-works/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter03/05_pitfall-when-using-context-for-object/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "web-vitals": "^1.0.1" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /chapter03/05_pitfall-when-using-context-for-object/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter03/05_pitfall-when-using-context-for-object/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | memo, 3 | createContext, 4 | useContext, 5 | useRef, 6 | useEffect, 7 | useState, 8 | } from "react"; 9 | 10 | const CountContext = createContext({ count1: 0, count2: 0 }); 11 | 12 | const Counter1 = () => { 13 | const { count1 } = useContext(CountContext); 14 | const renderCount = useRef(1); 15 | useEffect(() => { 16 | renderCount.current += 1; 17 | }); 18 | return ( 19 |
20 | Count1: {count1} (renders: {renderCount.current}) 21 |
22 | ); 23 | }; 24 | 25 | const MemoedCounter1 = memo(Counter1); 26 | 27 | const Counter2 = () => { 28 | const { count2 } = useContext(CountContext); 29 | const renderCount = useRef(1); 30 | useEffect(() => { 31 | renderCount.current += 1; 32 | }); 33 | return ( 34 |
35 | Count2: {count2} (renders: {renderCount.current}) 36 |
37 | ); 38 | }; 39 | 40 | const MemoedCounter2 = memo(Counter2); 41 | 42 | const Parent = () => ( 43 | <> 44 | 45 | 46 | 47 | ); 48 | 49 | const App = () => { 50 | const [count1, setCount1] = useState(0); 51 | const [count2, setCount2] = useState(0); 52 | return ( 53 | 54 | 55 | 56 | 57 | 58 | ); 59 | }; 60 | 61 | export default App; 62 | -------------------------------------------------------------------------------- /chapter03/05_pitfall-when-using-context-for-object/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter03/05_pitfall-when-using-context-for-object/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter03/05_pitfall-when-using-context-for-object/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter03/06_creating-small-state-pieces/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "web-vitals": "^1.0.1" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /chapter03/06_creating-small-state-pieces/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter03/06_creating-small-state-pieces/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Dispatch, 3 | SetStateAction, 4 | ReactNode, 5 | createContext, 6 | useContext, 7 | useState, 8 | } from "react"; 9 | 10 | type CountContextType = [number, Dispatch>]; 11 | 12 | const Count1Context = createContext([0, () => {}]); 13 | const Count2Context = createContext([0, () => {}]); 14 | 15 | const Counter1 = () => { 16 | const [count1, setCount1] = useContext(Count1Context); 17 | return ( 18 |
19 | Count1: {count1}{" "} 20 | 21 |
22 | ); 23 | }; 24 | 25 | const Counter2 = () => { 26 | const [count2, setCount2] = useContext(Count2Context); 27 | return ( 28 |
29 | Count2: {count2}{" "} 30 | 31 |
32 | ); 33 | }; 34 | 35 | const Parent = () => ( 36 |
37 | 38 | 39 | 40 | 41 |
42 | ); 43 | 44 | const Count1Provider = ({ children }: { children: ReactNode }) => { 45 | const [count1, setCount1] = useState(0); 46 | return ( 47 | 48 | {children} 49 | 50 | ); 51 | }; 52 | 53 | const Count2Provider = ({ children }: { children: ReactNode }) => { 54 | const [count2, setCount2] = useState(0); 55 | return ( 56 | 57 | {children} 58 | 59 | ); 60 | }; 61 | 62 | const App = () => ( 63 | 64 | 65 | 66 | 67 | 68 | ); 69 | 70 | export default App; 71 | -------------------------------------------------------------------------------- /chapter03/06_creating-small-state-pieces/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter03/06_creating-small-state-pieces/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter03/06_creating-small-state-pieces/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter03/07_creating-one-state-with-userreducer-and-propagate-with-multiple-contexts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "web-vitals": "^1.0.1" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /chapter03/07_creating-one-state-with-userreducer-and-propagate-with-multiple-contexts/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter03/07_creating-one-state-with-userreducer-and-propagate-with-multiple-contexts/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Dispatch, 3 | ReactNode, 4 | createContext, 5 | useContext, 6 | useReducer, 7 | } from "react"; 8 | 9 | type Action = { type: "INC1" } | { type: "INC2" }; 10 | 11 | const Count1Context = createContext(0); 12 | const Count2Context = createContext(0); 13 | const DispatchContext = createContext>(() => {}); 14 | 15 | const Counter1 = () => { 16 | const count1 = useContext(Count1Context); 17 | const dispatch = useContext(DispatchContext); 18 | return ( 19 |
20 | Count1: {count1}{" "} 21 | 22 |
23 | ); 24 | }; 25 | 26 | const Counter2 = () => { 27 | const count2 = useContext(Count2Context); 28 | const dispatch = useContext(DispatchContext); 29 | return ( 30 |
31 | Count2: {count2}{" "} 32 | 33 |
34 | ); 35 | }; 36 | 37 | const Parent = () => ( 38 |
39 | 40 | 41 | 42 | 43 |
44 | ); 45 | 46 | const Provider = ({ children }: { children: ReactNode }) => { 47 | const [state, dispatch] = useReducer( 48 | (prev: { count1: number; count2: number }, action: Action) => { 49 | if (action.type === "INC1") { 50 | return { ...prev, count1: prev.count1 + 1 }; 51 | } 52 | if (action.type === "INC2") { 53 | return { ...prev, count2: prev.count2 + 1 }; 54 | } 55 | throw new Error("no matching action"); 56 | }, 57 | { 58 | count1: 0, 59 | count2: 0, 60 | } 61 | ); 62 | return ( 63 | 64 | 65 | 66 | {children} 67 | 68 | 69 | 70 | ); 71 | }; 72 | 73 | const App = () => ( 74 | 75 | 76 | 77 | ); 78 | 79 | export default App; 80 | -------------------------------------------------------------------------------- /chapter03/07_creating-one-state-with-userreducer-and-propagate-with-multiple-contexts/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter03/07_creating-one-state-with-userreducer-and-propagate-with-multiple-contexts/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter03/07_creating-one-state-with-userreducer-and-propagate-with-multiple-contexts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter03/08_creating-custom-hook-and-provider-component/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "web-vitals": "^1.0.1" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /chapter03/08_creating-custom-hook-and-provider-component/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter03/08_creating-custom-hook-and-provider-component/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Dispatch, 3 | SetStateAction, 4 | ReactNode, 5 | createContext, 6 | useContext, 7 | useState, 8 | } from "react"; 9 | 10 | type CountContextType = [number, Dispatch>]; 11 | 12 | const Count1Context = createContext(null); 13 | export const Count1Provider = ({ children }: { children: ReactNode }) => ( 14 | 15 | {children} 16 | 17 | ); 18 | export const useCount1 = () => { 19 | const value = useContext(Count1Context); 20 | if (value === null) throw new Error("Provider missing"); 21 | return value; 22 | }; 23 | 24 | const Count2Context = createContext(null); 25 | export const Count2Provider = ({ children }: { children: ReactNode }) => ( 26 | 27 | {children} 28 | 29 | ); 30 | export const useCount2 = () => { 31 | const value = useContext(Count2Context); 32 | if (value === null) throw new Error("Provider missing"); 33 | return value; 34 | }; 35 | 36 | const Counter1 = () => { 37 | const [count1, setCount1] = useCount1(); 38 | return ( 39 |
40 | Count1: {count1}{" "} 41 | 42 |
43 | ); 44 | }; 45 | 46 | const Counter2 = () => { 47 | const [count2, setCount2] = useCount2(); 48 | return ( 49 |
50 | Count2: {count2}{" "} 51 | 52 |
53 | ); 54 | }; 55 | 56 | const Parent = () => ( 57 |
58 | 59 | 60 | 61 | 62 |
63 | ); 64 | 65 | const App = () => ( 66 | 67 | 68 | 69 | 70 | 71 | ); 72 | 73 | export default App; 74 | -------------------------------------------------------------------------------- /chapter03/08_creating-custom-hook-and-provider-component/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter03/08_creating-custom-hook-and-provider-component/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter03/08_creating-custom-hook-and-provider-component/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter03/09_factory-pattern-with-custom-hook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "web-vitals": "^1.0.1" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /chapter03/09_factory-pattern-with-custom-hook/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter03/09_factory-pattern-with-custom-hook/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, createContext, useContext, useState } from "react"; 2 | 3 | const createStateContext = ( 4 | useValue: (init?: Value) => State 5 | ) => { 6 | const StateContext = createContext(null); 7 | const StateProvider = ({ 8 | initialValue, 9 | children, 10 | }: { 11 | initialValue?: Value; 12 | children?: ReactNode; 13 | }) => ( 14 | 15 | {children} 16 | 17 | ); 18 | const useContextState = () => { 19 | const value = useContext(StateContext); 20 | if (value === null) throw new Error("Provider missing"); 21 | return value; 22 | }; 23 | return [StateProvider, useContextState] as const; 24 | }; 25 | 26 | const useNumberState = (init?: number) => useState(init || 0); 27 | 28 | const [Count1Provider, useCount1] = createStateContext(useNumberState); 29 | const [Count2Provider, useCount2] = createStateContext(useNumberState); 30 | 31 | const Counter1 = () => { 32 | const [count1, setCount1] = useCount1(); 33 | return ( 34 |
35 | Count1: {count1}{" "} 36 | 37 |
38 | ); 39 | }; 40 | 41 | const Counter2 = () => { 42 | const [count2, setCount2] = useCount2(); 43 | return ( 44 |
45 | Count2: {count2}{" "} 46 | 47 |
48 | ); 49 | }; 50 | 51 | const Parent = () => ( 52 |
53 | 54 | 55 | 56 | 57 |
58 | ); 59 | 60 | const App = () => ( 61 | 62 | 63 | 64 | 65 | 66 | ); 67 | 68 | export default App; 69 | -------------------------------------------------------------------------------- /chapter03/09_factory-pattern-with-custom-hook/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter03/09_factory-pattern-with-custom-hook/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter03/09_factory-pattern-with-custom-hook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter03/10_avoiding-provider-nesting-reduceright/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "web-vitals": "^1.0.1" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /chapter03/10_avoiding-provider-nesting-reduceright/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter03/10_avoiding-provider-nesting-reduceright/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ReactNode, 3 | createContext, 4 | createElement, 5 | useContext, 6 | useState, 7 | } from "react"; 8 | 9 | const createStateContext = ( 10 | useValue: (init?: Value) => State 11 | ) => { 12 | const StateContext = createContext(null); 13 | const StateProvider = ({ 14 | initialValue, 15 | children, 16 | }: { 17 | initialValue?: Value; 18 | children?: ReactNode; 19 | }) => ( 20 | 21 | {children} 22 | 23 | ); 24 | const useContextState = () => { 25 | const value = useContext(StateContext); 26 | if (value === null) throw new Error("Provider missing"); 27 | return value; 28 | }; 29 | return [StateProvider, useContextState] as const; 30 | }; 31 | 32 | const useNumberState = (init?: number) => useState(init || 0); 33 | 34 | const [Count1Provider, useCount1] = createStateContext(useNumberState); 35 | const [Count2Provider, useCount2] = createStateContext(useNumberState); 36 | const [Count3Provider, useCount3] = createStateContext(useNumberState); 37 | const [Count4Provider, useCount4] = createStateContext(useNumberState); 38 | const [Count5Provider, useCount5] = createStateContext(useNumberState); 39 | 40 | const Counter1 = () => { 41 | const [count1, setCount1] = useCount1(); 42 | return ( 43 |
44 | Count1: {count1}{" "} 45 | 46 |
47 | ); 48 | }; 49 | 50 | const Counter2 = () => { 51 | const [count2, setCount2] = useCount2(); 52 | return ( 53 |
54 | Count2: {count2}{" "} 55 | 56 |
57 | ); 58 | }; 59 | 60 | const Parent = () => ( 61 |
62 | 63 | 64 | 65 | 66 |
67 | ); 68 | 69 | const App = () => { 70 | const providers = [ 71 | [Count1Provider, { initialValue: 10 }], 72 | [Count2Provider, { initialValue: 20 }], 73 | [Count3Provider, { initialValue: 30 }], 74 | [Count4Provider, { initialValue: 40 }], 75 | [Count5Provider, { initialValue: 50 }], 76 | ] as const; 77 | return providers.reduceRight( 78 | (children, [Component, props]) => createElement(Component, props, children), 79 | 80 | ); 81 | }; 82 | 83 | /* 84 | const App = () => ( 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | ); 97 | */ 98 | 99 | export default App; 100 | -------------------------------------------------------------------------------- /chapter03/10_avoiding-provider-nesting-reduceright/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter03/10_avoiding-provider-nesting-reduceright/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter03/10_avoiding-provider-nesting-reduceright/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter04/01_naive_solution_to_module_state/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "web-vitals": "^1.0.1" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /chapter04/01_naive_solution_to_module_state/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter04/01_naive_solution_to_module_state/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | let count = 0; 4 | const setStateFunctions = new Set<(count: number) =>void>(); 5 | 6 | const Component1 = () => { 7 | const [state, setState] = useState(count); 8 | useEffect(() => { 9 | setStateFunctions.add(setState); 10 | return () => { 11 | setStateFunctions.delete(setState); 12 | } 13 | }, []); 14 | const inc = () => { 15 | count += 1; 16 | setStateFunctions.forEach((fn) => { 17 | fn(count); 18 | }); 19 | }; 20 | return ( 21 |
22 | {state} 23 |
24 | ); 25 | }; 26 | 27 | const Component2 = () => { 28 | const [state, setState] = useState(count); 29 | useEffect(() => { 30 | setStateFunctions.add(setState); 31 | return () => { 32 | setStateFunctions.delete(setState); 33 | } 34 | }, []); 35 | const inc2 = () => { 36 | count += 2; 37 | setStateFunctions.forEach((fn) => { 38 | fn(count); 39 | }); 40 | }; 41 | return ( 42 |
43 | {state} 44 |
45 | ); 46 | }; 47 | 48 | const App = () => ( 49 | <> 50 | 51 | 52 | 53 | ); 54 | 55 | export default App; 56 | -------------------------------------------------------------------------------- /chapter04/01_naive_solution_to_module_state/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter04/01_naive_solution_to_module_state/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter04/01_naive_solution_to_module_state/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter04/02_use_store_with_subscription/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "web-vitals": "^1.0.1" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /chapter04/02_use_store_with_subscription/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter04/02_use_store_with_subscription/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | type Store = { 4 | getState: () => T; 5 | setState: (action: T | ((prev: T) => T)) => void; 6 | subscribe: (callback: () => void) => () => void; 7 | }; 8 | 9 | const createStore = (initialState: T): Store => { 10 | let state = initialState; 11 | const callbacks = new Set<() => void>(); 12 | const getState = () => state; 13 | const setState = (nextState: T | ((prev: T) => T)) => { 14 | state = 15 | typeof nextState === "function" 16 | ? (nextState as (prev: T) => T)(state) 17 | : nextState; 18 | callbacks.forEach((callback) => callback()); 19 | }; 20 | const subscribe = (callback: () => void) => { 21 | callbacks.add(callback); 22 | return () => { 23 | callbacks.delete(callback); 24 | }; 25 | }; 26 | return { getState, setState, subscribe }; 27 | }; 28 | 29 | const store = createStore({ count: 0 }); 30 | 31 | const useStore = (store: Store) => { 32 | const [state, setState] = useState(store.getState()); 33 | useEffect(() => { 34 | const unsubscribe = store.subscribe(() => { 35 | setState(store.getState()); 36 | }); 37 | setState(store.getState()); 38 | return unsubscribe; 39 | }, [store]); 40 | return [state, store.setState] as const; 41 | }; 42 | 43 | const Component1 = () => { 44 | const [state, setState] = useStore(store); 45 | const inc = () => { 46 | setState((prev) => ({ 47 | ...prev, 48 | count: prev.count + 1, 49 | })); 50 | }; 51 | return ( 52 |
53 | {state.count} 54 |
55 | ); 56 | }; 57 | 58 | const Component2 = () => { 59 | const [state, setState] = useStore(store); 60 | const inc2 = () => { 61 | setState((prev) => ({ 62 | ...prev, 63 | count: prev.count + 2, 64 | })); 65 | }; 66 | return ( 67 |
68 | {state.count} 69 |
70 | ); 71 | }; 72 | 73 | const App = () => ( 74 | <> 75 | 76 | 77 | 78 | ); 79 | 80 | export default App; 81 | -------------------------------------------------------------------------------- /chapter04/02_use_store_with_subscription/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter04/02_use_store_with_subscription/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter04/02_use_store_with_subscription/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter04/03_use_store_with_selector/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "web-vitals": "^1.0.1" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /chapter04/03_use_store_with_selector/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter04/03_use_store_with_selector/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from "react"; 2 | 3 | type Store = { 4 | getState: () => T; 5 | setState: (action: T | ((prev: T) => T)) => void; 6 | subscribe: (callback: () => void) => () => void; 7 | }; 8 | 9 | const createStore = (initialState: T): Store => { 10 | let state = initialState; 11 | const callbacks = new Set<() => void>(); 12 | const getState = () => state; 13 | const setState = (nextState: T | ((prev: T) => T)) => { 14 | state = 15 | typeof nextState === "function" 16 | ? (nextState as (prev: T) => T)(state) 17 | : nextState; 18 | callbacks.forEach((callback) => callback()); 19 | }; 20 | const subscribe = (callback: () => void) => { 21 | callbacks.add(callback); 22 | return () => { 23 | callbacks.delete(callback); 24 | }; 25 | }; 26 | return { getState, setState, subscribe }; 27 | }; 28 | 29 | const store = createStore({ count1: 0, count2: 0 }); 30 | 31 | const useStoreSelector = (store: Store, selector: (state: T) => S) => { 32 | const [state, setState] = useState(() => selector(store.getState())); 33 | useEffect(() => { 34 | const unsubscribe = store.subscribe(() => { 35 | setState(selector(store.getState())); 36 | }); 37 | setState(selector(store.getState())); 38 | return unsubscribe; 39 | }, [store, selector]); 40 | return state; 41 | }; 42 | 43 | const Component1 = () => { 44 | const state = useStoreSelector( 45 | store, 46 | useCallback((state) => state.count1, []) 47 | ); 48 | const inc = () => { 49 | store.setState((prev) => ({ 50 | ...prev, 51 | count1: prev.count1 + 1, 52 | })); 53 | }; 54 | return ( 55 |
56 | count1: {state} 57 |
58 | ); 59 | }; 60 | 61 | const selectCount2 = (state: ReturnType) => state.count2; 62 | 63 | const Component2 = () => { 64 | const state = useStoreSelector(store, selectCount2); 65 | const inc = () => { 66 | store.setState((prev) => ({ 67 | ...prev, 68 | count2: prev.count2 + 1, 69 | })); 70 | }; 71 | return ( 72 |
73 | count2: {state} 74 |
75 | ); 76 | }; 77 | 78 | const App = () => ( 79 | <> 80 | 81 | 82 | 83 | 84 | 85 | ); 86 | 87 | export default App; 88 | -------------------------------------------------------------------------------- /chapter04/03_use_store_with_selector/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter04/03_use_store_with_selector/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter04/03_use_store_with_selector/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter04/04_use_subscription_with_store/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "@types/use-subscription": "^1.0.0", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "react-scripts": "4.0.3", 17 | "typescript": "^4.1.2", 18 | "use-subscription": "^1.5.1", 19 | "web-vitals": "^1.0.1" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /chapter04/04_use_subscription_with_store/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter04/04_use_subscription_with_store/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useMemo } from "react"; 2 | import { useSubscription } from "use-subscription"; 3 | 4 | type Store = { 5 | getState: () => T; 6 | setState: (action: T | ((prev: T) => T)) => void; 7 | subscribe: (callback: () => void) => () => void; 8 | }; 9 | 10 | const createStore = (initialState: T): Store => { 11 | let state = initialState; 12 | const callbacks = new Set<() => void>(); 13 | const getState = () => state; 14 | const setState = (nextState: T | ((prev: T) => T)) => { 15 | state = 16 | typeof nextState === "function" 17 | ? (nextState as (prev: T) => T)(state) 18 | : nextState; 19 | callbacks.forEach((callback) => callback()); 20 | }; 21 | const subscribe = (callback: () => void) => { 22 | callbacks.add(callback); 23 | return () => { 24 | callbacks.delete(callback); 25 | }; 26 | }; 27 | return { getState, setState, subscribe }; 28 | }; 29 | 30 | const store = createStore({ count1: 0, count2: 0 }); 31 | 32 | const useStoreSelector = (store: Store, selector: (state: T) => S) => 33 | useSubscription( 34 | useMemo( 35 | () => ({ 36 | getCurrentValue: () => selector(store.getState()), 37 | subscribe: store.subscribe, 38 | }), 39 | [store, selector] 40 | ) 41 | ); 42 | 43 | const Component1 = () => { 44 | const state = useStoreSelector( 45 | store, 46 | useCallback((state) => state.count1, []) 47 | ); 48 | /* 49 | const state = useSubscription(useMemo(() => ({ 50 | getCurrentValue: () => store.getState().count1, 51 | subscribe: store.subscribe, 52 | }), [])); 53 | */ 54 | const inc = () => { 55 | store.setState((prev) => ({ 56 | ...prev, 57 | count1: prev.count1 + 1, 58 | })); 59 | }; 60 | return ( 61 |
62 | count1: {state} 63 |
64 | ); 65 | }; 66 | 67 | const selectCount2 = (state: ReturnType) => state.count2; 68 | 69 | const Component2 = () => { 70 | const state = useStoreSelector(store, selectCount2); 71 | const inc = () => { 72 | store.setState((prev) => ({ 73 | ...prev, 74 | count2: prev.count2 + 1, 75 | })); 76 | }; 77 | return ( 78 |
79 | count2: {state} 80 |
81 | ); 82 | }; 83 | 84 | const App = () => ( 85 | <> 86 | 87 | 88 | 89 | 90 | 91 | ); 92 | 93 | export default App; 94 | -------------------------------------------------------------------------------- /chapter04/04_use_subscription_with_store/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter04/04_use_subscription_with_store/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter04/04_use_subscription_with_store/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter05/01_combine_context_and_subscription/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "@types/use-subscription": "^1.0.0", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "react-scripts": "4.0.3", 17 | "typescript": "^4.1.2", 18 | "use-subscription": "^1.5.1", 19 | "web-vitals": "^1.0.1" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /chapter05/01_combine_context_and_subscription/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter05/01_combine_context_and_subscription/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, createContext, useContext, useRef, useMemo } from "react"; 2 | import { useSubscription } from "use-subscription"; 3 | 4 | type Store = { 5 | getState: () => T; 6 | setState: (action: T | ((prev: T) => T)) => void; 7 | subscribe: (callback: () => void) => () => void; 8 | }; 9 | 10 | const createStore = (initialState: T): Store => { 11 | let state = initialState; 12 | const callbacks = new Set<() => void>(); 13 | const getState = () => state; 14 | const setState = (nextState: T | ((prev: T) => T)) => { 15 | state = 16 | typeof nextState === "function" 17 | ? (nextState as (prev: T) => T)(state) 18 | : nextState; 19 | callbacks.forEach((callback) => callback()); 20 | }; 21 | const subscribe = (callback: () => void) => { 22 | callbacks.add(callback); 23 | return () => { 24 | callbacks.delete(callback); 25 | }; 26 | }; 27 | return { getState, setState, subscribe }; 28 | }; 29 | 30 | type State = { count: number; text?: string }; 31 | 32 | const StoreContext = createContext>( 33 | createStore({ count: 0, text: "hello" }) 34 | ); 35 | 36 | const StoreProvider = ({ 37 | initialState, 38 | children, 39 | }: { 40 | initialState: State; 41 | children: ReactNode; 42 | }) => { 43 | const storeRef = useRef>(); 44 | if (!storeRef.current) { 45 | storeRef.current = createStore(initialState); 46 | } 47 | return ( 48 | 49 | {children} 50 | 51 | ); 52 | }; 53 | 54 | const useSelector = (selector: (state: State) => S) => { 55 | const store = useContext(StoreContext); 56 | return useSubscription( 57 | useMemo( 58 | () => ({ 59 | getCurrentValue: () => selector(store.getState()), 60 | subscribe: store.subscribe, 61 | }), 62 | [store, selector] 63 | ) 64 | ); 65 | }; 66 | 67 | const useSetState = () => { 68 | const store = useContext(StoreContext); 69 | return store.setState; 70 | }; 71 | 72 | const selectCount = (state: State) => state.count; 73 | 74 | const Component = () => { 75 | const count = useSelector(selectCount); 76 | const setState = useSetState(); 77 | const inc = () => { 78 | setState((prev) => ({ 79 | ...prev, 80 | count: prev.count + 1, 81 | })); 82 | }; 83 | return ( 84 |
85 | count: {count} 86 |
87 | ); 88 | }; 89 | 90 | const App = () => ( 91 | <> 92 |

Using default store

93 | 94 | 95 | 96 |

Using store provider

97 | 98 | 99 | 100 |

Using inner store provider

101 | 102 | 103 |
104 |
105 | 106 | ); 107 | 108 | export default App; 109 | -------------------------------------------------------------------------------- /chapter05/01_combine_context_and_subscription/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter05/01_combine_context_and_subscription/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter05/01_combine_context_and_subscription/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter07/01_counter_example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "web-vitals": "^1.0.1", 18 | "zustand": "^3.5.10" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter07/01_counter_example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter07/01_counter_example/src/App.tsx: -------------------------------------------------------------------------------- 1 | import create from "zustand"; 2 | 3 | type StoreState = { 4 | count1: number; 5 | count2: number; 6 | inc1: () => void; 7 | inc2: () => void; 8 | }; 9 | 10 | const useStore = create((set) => ({ 11 | count1: 0, 12 | count2: 0, 13 | inc1: () => set((prev) => ({ count1: prev.count1 + 1 })), 14 | inc2: () => set((prev) => ({ count2: prev.count2 + 1 })), 15 | })); 16 | 17 | const selectCount1 = (state: StoreState) => state.count1; 18 | const selectInc1 = (state: StoreState) => state.inc1; 19 | 20 | const Counter1 = () => { 21 | const count1 = useStore(selectCount1); 22 | const inc1 = useStore(selectInc1); 23 | return ( 24 |
25 | count1: {count1} 26 |
27 | ); 28 | }; 29 | 30 | const selectCount2 = (state: StoreState) => state.count2; 31 | const selectInc2 = (state: StoreState) => state.inc2; 32 | 33 | const Counter2 = () => { 34 | const count2 = useStore(selectCount2); 35 | const inc2 = useStore(selectInc2); 36 | return ( 37 |
38 | count2: {count2} 39 |
40 | ); 41 | }; 42 | 43 | const selectTotal = (state: StoreState) => state.count1 + state.count2; 44 | 45 | const Total = () => { 46 | const total = useStore(selectTotal); 47 | return ( 48 |
49 | total: {total} 50 |
51 | ); 52 | }; 53 | 54 | const App = () => ( 55 | <> 56 | 57 | 58 | 59 | 60 | ); 61 | 62 | export default App; 63 | -------------------------------------------------------------------------------- /chapter07/01_counter_example/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter07/01_counter_example/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter07/01_counter_example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter07/02_todo_example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "web-vitals": "^1.0.1", 18 | "zustand": "^3.5.10" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter07/02_todo_example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter07/02_todo_example/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { memo, useState } from "react"; 2 | import create from "zustand"; 3 | 4 | type Todo = { 5 | id: number; 6 | title: string; 7 | done: boolean; 8 | }; 9 | 10 | type StoreState = { 11 | todos: Todo[]; 12 | addTodo: (title: string) => void; 13 | removeTodo: (id: number) => void; 14 | toggleTodo: (id: number) => void; 15 | }; 16 | 17 | let nextId = 0; 18 | 19 | const useStore = create((set) => ({ 20 | todos: [], 21 | addTodo: (title) => 22 | set((prev) => ({ 23 | todos: [...prev.todos, { id: ++nextId, title, done: false }], 24 | })), 25 | removeTodo: (id) => 26 | set((prev) => ({ 27 | todos: prev.todos.filter((todo) => todo.id !== id), 28 | })), 29 | toggleTodo: (id) => 30 | set((prev) => ({ 31 | todos: prev.todos.map((todo) => 32 | todo.id === id ? { ...todo, done: !todo.done } : todo 33 | ), 34 | })), 35 | })); 36 | 37 | const selectRemoveTodo = (state: StoreState) => state.removeTodo; 38 | const selectToggleTodo = (state: StoreState) => state.toggleTodo; 39 | 40 | const TodoItem = ({ todo }: { todo: Todo }) => { 41 | const removeTodo = useStore(selectRemoveTodo); 42 | const toggleTodo = useStore(selectToggleTodo); 43 | return ( 44 |
45 | toggleTodo(todo.id)} 49 | /> 50 | 55 | {todo.title} 56 | 57 | 58 |
59 | ); 60 | }; 61 | 62 | const MemoedTodoItem = memo(TodoItem); 63 | 64 | const selectTodos = (state: StoreState) => state.todos; 65 | 66 | const TodoList = () => { 67 | const todos = useStore(selectTodos); 68 | return ( 69 |
70 | {todos.map((todo) => ( 71 | 72 | ))} 73 |
74 | ); 75 | }; 76 | 77 | const selectAddTodo = (state: StoreState) => state.addTodo; 78 | 79 | const NewTodo = () => { 80 | const addTodo = useStore(selectAddTodo); 81 | const [text, setText] = useState(""); 82 | const onClick = () => { 83 | addTodo(text); 84 | setText(""); 85 | }; 86 | return ( 87 |
88 | setText(e.target.value)} /> 89 | 92 |
93 | ); 94 | }; 95 | 96 | const App = () => ( 97 | <> 98 | 99 | 100 | 101 | ); 102 | 103 | export default App; 104 | -------------------------------------------------------------------------------- /chapter07/02_todo_example/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter07/02_todo_example/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter07/02_todo_example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter08/01_comparison/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "jotai": "^1.3.5", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "react-scripts": "4.0.3", 17 | "typescript": "^4.1.2", 18 | "web-vitals": "^1.0.1" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter08/01_comparison/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter08/01_comparison/src/AppWithContext.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Dispatch, 3 | ReactNode, 4 | SetStateAction, 5 | createContext, 6 | useContext, 7 | useState, 8 | } from "react"; 9 | 10 | const CountContext = createContext( 11 | (undefined as unknown) as [number, Dispatch>] 12 | ); 13 | 14 | const CountProvider = ({ children }: { children: ReactNode }) => ( 15 | {children} 16 | ); 17 | 18 | const Counter1 = () => { 19 | const [count, setCount] = useContext(CountContext); 20 | const inc = () => setCount((c) => c + 1); 21 | return ( 22 | <> 23 | {count} 24 | 25 | ); 26 | }; 27 | 28 | const Counter2 = () => { 29 | const [count, setCount] = useContext(CountContext); 30 | const inc = () => setCount((c) => c + 1); 31 | return ( 32 | <> 33 | {count} 34 | 35 | ); 36 | }; 37 | 38 | const App = () => ( 39 | 40 |
41 | 42 |
43 |
44 | 45 |
46 |
47 | ); 48 | 49 | export default App; 50 | -------------------------------------------------------------------------------- /chapter08/01_comparison/src/AppWithJotai.tsx: -------------------------------------------------------------------------------- 1 | import { atom, useAtom } from "jotai"; 2 | 3 | const countAtom = atom(0); 4 | 5 | const Counter1 = () => { 6 | const [count, setCount] = useAtom(countAtom); 7 | const inc = () => setCount((c) => c + 1); 8 | return ( 9 | <> 10 | {count} 11 | 12 | ); 13 | }; 14 | 15 | const Counter2 = () => { 16 | const [count, setCount] = useAtom(countAtom); 17 | const inc = () => setCount((c) => c + 1); 18 | return ( 19 | <> 20 | {count} 21 | 22 | ); 23 | }; 24 | 25 | const App = () => ( 26 | <> 27 |
28 | 29 |
30 |
31 | 32 |
33 | 34 | ); 35 | 36 | export default App; 37 | -------------------------------------------------------------------------------- /chapter08/01_comparison/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import AppWithContext from './AppWithContext'; 4 | import AppWithJotai from './AppWithJotai'; 5 | 6 | ReactDOM.render( 7 | 8 |

App with Context

9 | 10 |

App with Jotai

11 | 12 |
, 13 | document.getElementById('root') 14 | ); 15 | -------------------------------------------------------------------------------- /chapter08/01_comparison/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter08/01_comparison/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter08/02_counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "jotai": "^1.3.5", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "react-scripts": "4.0.3", 17 | "typescript": "^4.1.2", 18 | "web-vitals": "^1.0.1" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter08/02_counter/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter08/02_counter/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { atom, useAtom } from "jotai"; 2 | 3 | const count1Atom = atom(0); 4 | const count2Atom = atom(0); 5 | 6 | const Counter = ({ countAtom }: { countAtom: typeof count1Atom }) => { 7 | const [count, setCount] = useAtom(countAtom); 8 | const inc = () => setCount((c) => c + 1); 9 | return ( 10 | <> 11 | {count} 12 | 13 | ); 14 | }; 15 | 16 | const totalAtom = atom((get) => get(count1Atom) + get(count2Atom)); 17 | 18 | const Total = () => { 19 | const [total] = useAtom(totalAtom); 20 | return <>{total}; 21 | }; 22 | 23 | const App = () => ( 24 | <> 25 | () + () 26 | = 27 | 28 | ); 29 | 30 | export default App; 31 | -------------------------------------------------------------------------------- /chapter08/02_counter/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter08/02_counter/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter08/02_counter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter08/03_provider/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "jotai": "^1.3.5", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "react-scripts": "4.0.3", 17 | "typescript": "^4.1.2", 18 | "web-vitals": "^1.0.1" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter08/03_provider/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter08/03_provider/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { atom, useAtom, Provider } from "jotai"; 2 | 3 | const countAtom = atom(0); 4 | 5 | const Counter = () => { 6 | const [count, setCount] = useAtom(countAtom); 7 | const inc = () => setCount((c) => c + 1); 8 | return ( 9 | <> 10 | {count} 11 | 12 | ); 13 | }; 14 | 15 | const App = () => ( 16 | <> 17 | 18 |

First Provider

19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 |

Second Provider

28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 | 36 | ); 37 | 38 | export default App; 39 | -------------------------------------------------------------------------------- /chapter08/03_provider/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter08/03_provider/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter08/03_provider/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter08/04_todo_app_single_atom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "jotai": "^1.3.5", 14 | "nanoid": "^3.1.25", 15 | "react": "^17.0.2", 16 | "react-dom": "^17.0.2", 17 | "react-scripts": "4.0.3", 18 | "typescript": "^4.1.2", 19 | "web-vitals": "^1.0.1" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /chapter08/04_todo_app_single_atom/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter08/04_todo_app_single_atom/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { memo, useCallback, useState } from "react"; 2 | import { atom, useAtom } from "jotai"; 3 | import { nanoid } from "nanoid"; 4 | 5 | type Todo = { 6 | id: string; 7 | title: string; 8 | done: boolean; 9 | }; 10 | 11 | const todosAtom = atom([]); 12 | 13 | const TodoItem = ({ 14 | todo, 15 | removeTodo, 16 | toggleTodo, 17 | }: { 18 | todo: Todo; 19 | removeTodo: (id: string) => void; 20 | toggleTodo: (id: string) => void; 21 | }) => { 22 | return ( 23 |
24 | toggleTodo(todo.id)} 28 | /> 29 | 34 | {todo.title} 35 | 36 | 37 |
38 | ); 39 | }; 40 | 41 | const MemoedTodoItem = memo(TodoItem); 42 | 43 | const TodoList = () => { 44 | const [todos, setTodos] = useAtom(todosAtom); 45 | const removeTodo = useCallback( 46 | (id: string) => setTodos((prev) => prev.filter((item) => item.id !== id)), 47 | [setTodos] 48 | ); 49 | const toggleTodo = useCallback( 50 | (id: string) => 51 | setTodos((prev) => 52 | prev.map((item) => 53 | item.id === id ? { ...item, done: !item.done } : item 54 | ) 55 | ), 56 | [setTodos] 57 | ); 58 | return ( 59 |
60 | {todos.map((todo) => ( 61 | 67 | ))} 68 |
69 | ); 70 | }; 71 | 72 | const NewTodo = () => { 73 | const [, setTodos] = useAtom(todosAtom); 74 | const [text, setText] = useState(""); 75 | const onClick = () => { 76 | setTodos((prev) => [...prev, { id: nanoid(), title: text, done: false }]); 77 | setText(""); 78 | }; 79 | return ( 80 |
81 | setText(e.target.value)} /> 82 | 85 |
86 | ); 87 | }; 88 | 89 | const App = () => ( 90 | <> 91 | 92 | 93 | 94 | ); 95 | 96 | export default App; 97 | -------------------------------------------------------------------------------- /chapter08/04_todo_app_single_atom/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter08/04_todo_app_single_atom/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter08/04_todo_app_single_atom/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter08/05_todo_app_atoms_in_atom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "jotai": "^1.3.5", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "react-scripts": "4.0.3", 17 | "typescript": "^4.1.2", 18 | "web-vitals": "^1.0.1" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter08/05_todo_app_atoms_in_atom/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter08/05_todo_app_atoms_in_atom/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { memo, useCallback, useState } from "react"; 2 | import { PrimitiveAtom, atom, useAtom } from "jotai"; 3 | 4 | type Todo = { 5 | title: string; 6 | done: boolean; 7 | }; 8 | 9 | type TodoAtom = PrimitiveAtom; 10 | 11 | const todoAtomsAtom = atom([]); 12 | 13 | const TodoItem = ({ 14 | todoAtom, 15 | remove, 16 | }: { 17 | todoAtom: TodoAtom; 18 | remove: (todoAtom: TodoAtom) => void; 19 | }) => { 20 | const [todo, setTodo] = useAtom(todoAtom); 21 | return ( 22 |
23 | setTodo((prev) => ({ ...prev, done: !prev.done }))} 27 | /> 28 | 33 | {todo.title} 34 | 35 | 36 |
37 | ); 38 | }; 39 | 40 | const MemoedTodoItem = memo(TodoItem); 41 | 42 | const TodoList = () => { 43 | const [todoAtoms, setTodoAtoms] = useAtom(todoAtomsAtom); 44 | const remove = useCallback( 45 | (todoAtom: TodoAtom) => 46 | setTodoAtoms((prev) => prev.filter((item) => item !== todoAtom)), 47 | [setTodoAtoms] 48 | ); 49 | return ( 50 |
51 | {todoAtoms.map((todoAtom) => ( 52 | 57 | ))} 58 |
59 | ); 60 | }; 61 | 62 | const NewTodo = () => { 63 | const [, setTodoAtoms] = useAtom(todoAtomsAtom); 64 | const [text, setText] = useState(""); 65 | const onClick = () => { 66 | setTodoAtoms((prev) => [...prev, atom({ title: text, done: false })]); 67 | setText(""); 68 | }; 69 | return ( 70 |
71 | setText(e.target.value)} /> 72 | 75 |
76 | ); 77 | }; 78 | 79 | const App = () => ( 80 | <> 81 | 82 | 83 | 84 | ); 85 | 86 | export default App; 87 | -------------------------------------------------------------------------------- /chapter08/05_todo_app_atoms_in_atom/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter08/05_todo_app_atoms_in_atom/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter08/05_todo_app_atoms_in_atom/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter09/01_counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "valtio": "^1.2.4", 18 | "web-vitals": "^1.0.1" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter09/01_counter/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter09/01_counter/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { proxy, useSnapshot } from "valtio"; 2 | 3 | const state = proxy({ 4 | count1: 0, 5 | count2: 0, 6 | }); 7 | 8 | const Counter1 = () => { 9 | const snap = useSnapshot(state); 10 | const inc = () => ++state.count1; 11 | return ( 12 | <> 13 | {snap.count1} 14 | 15 | ); 16 | }; 17 | 18 | const Counter2 = () => { 19 | const snap = useSnapshot(state); 20 | const inc = () => ++state.count2; 21 | return ( 22 | <> 23 | {snap.count2} 24 | 25 | ); 26 | }; 27 | 28 | const App = () => ( 29 | <> 30 |
31 | 32 |
33 |
34 | 35 |
36 | 37 | ); 38 | 39 | export default App; 40 | -------------------------------------------------------------------------------- /chapter09/01_counter/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter09/01_counter/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter09/01_counter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter09/02_todo_app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "nanoid": "^3.1.25", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "react-scripts": "4.0.3", 17 | "typescript": "^4.1.2", 18 | "valtio": "^1.2.4", 19 | "web-vitals": "^1.0.1" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /chapter09/02_todo_app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter09/02_todo_app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { memo, useState } from "react"; 2 | import { proxy, useSnapshot } from "valtio"; 3 | import { nanoid } from "nanoid"; 4 | 5 | type Todo = { 6 | id: string; 7 | title: string; 8 | done: boolean; 9 | }; 10 | 11 | const state = proxy<{ todos: Todo[] }>({ 12 | todos: [], 13 | }); 14 | 15 | const createTodo = (title: string) => { 16 | state.todos.push({ 17 | id: nanoid(), 18 | title, 19 | done: false, 20 | }); 21 | }; 22 | 23 | const removeTodo = (id: string) => { 24 | const index = state.todos.findIndex((item) => item.id === id); 25 | state.todos.splice(index, 1); 26 | }; 27 | 28 | const toggleTodo = (id: string) => { 29 | const index = state.todos.findIndex((item) => item.id === id); 30 | state.todos[index].done = !state.todos[index].done; 31 | }; 32 | 33 | const TodoItem = ({ 34 | id, 35 | title, 36 | done, 37 | }: { 38 | id: string; 39 | title: string; 40 | done: boolean; 41 | }) => { 42 | return ( 43 |
44 | toggleTodo(id)} /> 45 | 50 | {title} 51 | 52 | 53 |
54 | ); 55 | }; 56 | 57 | const MemoedTodoItem = memo(TodoItem); 58 | 59 | const TodoList = () => { 60 | const { todos } = useSnapshot(state); 61 | return ( 62 |
63 | {todos.map((todo) => ( 64 | 70 | ))} 71 |
72 | ); 73 | }; 74 | 75 | const NewTodo = () => { 76 | const [text, setText] = useState(""); 77 | const onClick = () => { 78 | createTodo(text); 79 | setText(""); 80 | }; 81 | return ( 82 |
83 | setText(e.target.value)} /> 84 | 87 |
88 | ); 89 | }; 90 | 91 | const App = () => ( 92 | <> 93 | 94 | 95 | 96 | ); 97 | 98 | export default App; 99 | -------------------------------------------------------------------------------- /chapter09/02_todo_app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter09/02_todo_app/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter09/02_todo_app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter09/03_another_todo_app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "nanoid": "^3.1.25", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "react-scripts": "4.0.3", 17 | "typescript": "^4.1.2", 18 | "valtio": "^1.2.4", 19 | "web-vitals": "^1.0.1" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /chapter09/03_another_todo_app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter09/03_another_todo_app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { memo, useState } from "react"; 2 | import { proxy, useSnapshot } from "valtio"; 3 | import { nanoid } from "nanoid"; 4 | 5 | type Todo = { 6 | id: string; 7 | title: string; 8 | done: boolean; 9 | }; 10 | 11 | const state = proxy<{ todos: Todo[] }>({ 12 | todos: [], 13 | }); 14 | 15 | const createTodo = (title: string) => { 16 | state.todos.push({ 17 | id: nanoid(), 18 | title, 19 | done: false, 20 | }); 21 | }; 22 | 23 | const removeTodo = (id: string) => { 24 | const index = state.todos.findIndex((item) => item.id === id); 25 | state.todos.splice(index, 1); 26 | }; 27 | 28 | const toggleTodo = (id: string) => { 29 | const index = state.todos.findIndex((item) => item.id === id); 30 | state.todos[index].done = !state.todos[index].done; 31 | }; 32 | 33 | const TodoItem = ({ id }: { id: string }) => { 34 | const todoState = state.todos.find((todo) => todo.id === id); 35 | if (!todoState) { 36 | throw new Error("invalid todo id"); 37 | } 38 | const { title, done } = useSnapshot(todoState); 39 | return ( 40 |
41 | toggleTodo(id)} /> 42 | 47 | {title} 48 | 49 | 50 |
51 | ); 52 | }; 53 | 54 | const MemoedTodoItem = memo(TodoItem); 55 | 56 | const TodoList = () => { 57 | const { todos } = useSnapshot(state); 58 | const todoIds = todos.map((todo) => todo.id); 59 | return ( 60 |
61 | {todoIds.map((todoId) => ( 62 | 63 | ))} 64 |
65 | ); 66 | }; 67 | 68 | const NewTodo = () => { 69 | const [text, setText] = useState(""); 70 | const onClick = () => { 71 | createTodo(text); 72 | setText(""); 73 | }; 74 | return ( 75 |
76 | setText(e.target.value)} /> 77 | 80 |
81 | ); 82 | }; 83 | 84 | const App = () => ( 85 | <> 86 | 87 | 88 | 89 | ); 90 | 91 | export default App; 92 | -------------------------------------------------------------------------------- /chapter09/03_another_todo_app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter09/03_another_todo_app/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter09/03_another_todo_app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter10/01_bare_context/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "web-vitals": "^1.0.1" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /chapter10/01_bare_context/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter10/01_bare_context/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, createContext, useContext, useState } from "react"; 2 | 3 | const useValue = () => useState({ count: 0, text: "hello" }); 4 | 5 | const StateContext = createContext | null>(null); 6 | 7 | const Provider = ({ children }: { children: ReactNode }) => ( 8 | {children} 9 | ); 10 | 11 | const useStateContext = () => { 12 | const contextValue = useContext(StateContext); 13 | if (contextValue === null) { 14 | throw new Error("Please use Provider"); 15 | } 16 | return contextValue; 17 | }; 18 | 19 | const Counter = () => { 20 | const [state, setState] = useStateContext(); 21 | const inc = () => { 22 | setState((prev) => ({ ...prev, count: prev.count + 1 })); 23 | }; 24 | return ( 25 |
26 | count: {state.count} 27 |
28 | ); 29 | }; 30 | 31 | const TextBox = () => { 32 | const [state, setState] = useStateContext(); 33 | const setText = (text: string) => { 34 | setState((prev) => ({ ...prev, text })); 35 | }; 36 | return ( 37 |
38 | setText(e.target.value)} /> 39 |
40 | ); 41 | }; 42 | 43 | const App = () => ( 44 | 45 |
46 | 47 | 48 | 49 | 50 |
51 |
52 | ); 53 | 54 | export default App; 55 | -------------------------------------------------------------------------------- /chapter10/01_bare_context/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter10/01_bare_context/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter10/01_bare_context/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter10/02_with_usestate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "react-tracked": "^1.7.4", 17 | "typescript": "^4.1.2", 18 | "web-vitals": "^1.0.1" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter10/02_with_usestate/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter10/02_with_usestate/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { createContainer } from "react-tracked"; 3 | 4 | const useValue = () => useState({ count: 0, text: "hello" }); 5 | 6 | const { Provider, useTracked } = createContainer(useValue); 7 | 8 | const Counter = () => { 9 | const [state, setState] = useTracked(); 10 | const inc = () => { 11 | setState((prev) => ({ ...prev, count: prev.count + 1 })); 12 | }; 13 | return ( 14 |
15 | count: {state.count} 16 |
17 | ); 18 | }; 19 | 20 | const TextBox = () => { 21 | const [state, setState] = useTracked(); 22 | const setText = (text: string) => { 23 | setState((prev) => ({ ...prev, text })); 24 | }; 25 | return ( 26 |
27 | setText(e.target.value)} /> 28 |
29 | ); 30 | }; 31 | 32 | const App = () => ( 33 | 34 |
35 | 36 | 37 | 38 | 39 |
40 |
41 | ); 42 | 43 | export default App; 44 | -------------------------------------------------------------------------------- /chapter10/02_with_usestate/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter10/02_with_usestate/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter10/02_with_usestate/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter10/03_with_usereducer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "react-tracked": "^1.7.4", 17 | "typescript": "^4.1.2", 18 | "web-vitals": "^1.0.1" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter10/03_with_usereducer/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter10/03_with_usereducer/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useReducer } from "react"; 2 | import { createContainer } from "react-tracked"; 3 | 4 | const useValue = () => { 5 | type State = { count: number; text: string }; 6 | type Action = { type: "INC" } | { type: "SET_TEXT"; text: string }; 7 | const [state, dispatch] = useReducer( 8 | (state: State, action: Action) => { 9 | if (action.type === "INC") { 10 | return { ...state, count: state.count + 1 }; 11 | } 12 | if (action.type === "SET_TEXT") { 13 | return { ...state, text: action.text }; 14 | } 15 | throw new Error("unknown action type"); 16 | }, 17 | { count: 0, text: "hello" } 18 | ); 19 | useEffect(() => { 20 | console.log("latest state", state); 21 | }, [state]); 22 | return [state, dispatch] as const; 23 | }; 24 | 25 | const { Provider, useTracked } = createContainer(useValue); 26 | 27 | const Counter = () => { 28 | const [state, dispatch] = useTracked(); 29 | const inc = () => dispatch({ type: "INC" }); 30 | return ( 31 |
32 | count: {state.count} 33 |
34 | ); 35 | }; 36 | 37 | const TextBox = () => { 38 | const [state, dispatch] = useTracked(); 39 | const setText = (text: string) => { 40 | dispatch({ type: "SET_TEXT", text }); 41 | }; 42 | return ( 43 |
44 | setText(e.target.value)} /> 45 |
46 | ); 47 | }; 48 | 49 | const App = () => ( 50 | 51 |
52 | 53 | 54 | 55 | 56 |
57 |
58 | ); 59 | 60 | export default App; 61 | -------------------------------------------------------------------------------- /chapter10/03_with_usereducer/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter10/03_with_usereducer/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter10/03_with_usereducer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter10/04_with_reactredux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-redux": "^7.2.6", 16 | "react-scripts": "4.0.3", 17 | "react-tracked": "^1.7.4", 18 | "redux": "^4.1.2", 19 | "typescript": "^4.1.2", 20 | "web-vitals": "^1.0.1" 21 | }, 22 | "scripts": { 23 | "start": "react-scripts start", 24 | "build": "react-scripts build", 25 | "test": "react-scripts test", 26 | "eject": "react-scripts eject" 27 | }, 28 | "eslintConfig": { 29 | "extends": [ 30 | "react-app", 31 | "react-app/jest" 32 | ] 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /chapter10/04_with_reactredux/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter10/04_with_reactredux/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { createStore } from "redux"; 2 | import { Provider, useDispatch, useSelector } from "react-redux"; 3 | import { createTrackedSelector } from "react-tracked"; 4 | 5 | type State = { count: number; text: string }; 6 | type Action = { type: "INC" } | { type: "SET_TEXT"; text: string }; 7 | 8 | const initialState: State = { count: 0, text: "hello" }; 9 | const reducer = (state = initialState, action: Action) => { 10 | if (action.type === "INC") { 11 | return { ...state, count: state.count + 1 }; 12 | } 13 | if (action.type === "SET_TEXT") { 14 | return { ...state, text: action.text }; 15 | } 16 | return state 17 | }; 18 | 19 | const store = createStore(reducer); 20 | const useTrackedState = createTrackedSelector(useSelector); 21 | 22 | const Counter = () => { 23 | const dispatch = useDispatch(); 24 | const { count } = useTrackedState(); 25 | const inc = () => dispatch({ type: "INC" }); 26 | return ( 27 |
28 | count: {count} 29 |
30 | ); 31 | }; 32 | 33 | const TextBox = () => { 34 | const dispatch = useDispatch(); 35 | const state = useTrackedState(); 36 | const setText = (text: string) => { 37 | dispatch({ type: "SET_TEXT", text }); 38 | }; 39 | return ( 40 |
41 | setText(e.target.value)} /> 42 |
43 | ); 44 | }; 45 | 46 | const App = () => ( 47 | 48 |
49 | 50 | 51 | 52 | 53 |
54 |
55 | ); 56 | 57 | export default App; 58 | -------------------------------------------------------------------------------- /chapter10/04_with_reactredux/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter10/04_with_reactredux/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter10/04_with_reactredux/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter11/01_redux_counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reduxjs/toolkit": "^1.6.2", 7 | "@testing-library/jest-dom": "^5.11.4", 8 | "@testing-library/react": "^11.1.0", 9 | "@testing-library/user-event": "^12.1.10", 10 | "@types/jest": "^26.0.15", 11 | "@types/node": "^12.0.0", 12 | "@types/react": "^17.0.0", 13 | "@types/react-dom": "^17.0.0", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "react-redux": "^7.2.6", 17 | "react-scripts": "4.0.3", 18 | "typescript": "^4.1.2", 19 | "web-vitals": "^1.0.1" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /chapter11/01_redux_counter/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter11/01_redux_counter/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Provider } from "react-redux"; 2 | import { store } from "./app/store"; 3 | import { Counter } from "./features/counter/Counter"; 4 | 5 | const App = () => ( 6 | 7 |
8 | 9 | 10 |
11 |
12 | ); 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /chapter11/01_redux_counter/src/app/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import counterReducer from "../features/counter/counterSlice"; 3 | 4 | export const store = configureStore({ 5 | reducer: { 6 | counter: counterReducer, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /chapter11/01_redux_counter/src/features/counter/Counter.tsx: -------------------------------------------------------------------------------- 1 | import { useSelector, useDispatch } from "react-redux"; 2 | import { decrement, increment } from "./counterSlice"; 3 | 4 | export function Counter() { 5 | const count = useSelector( 6 | (state: { counter: { value: number } }) => state.counter.value 7 | ); 8 | const dispatch = useDispatch(); 9 | return ( 10 |
11 | 12 | {count} 13 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /chapter11/01_redux_counter/src/features/counter/counterSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | value: 0, 5 | }; 6 | 7 | export const counterSlice = createSlice({ 8 | name: "counter", 9 | initialState, 10 | reducers: { 11 | increment: (state) => { 12 | state.value += 1; 13 | }, 14 | decrement: (state) => { 15 | state.value -= 1; 16 | }, 17 | incrementByAmount: (state, action: PayloadAction) => { 18 | state.value += action.payload; 19 | }, 20 | }, 21 | }); 22 | 23 | export const { increment, decrement, incrementByAmount } = counterSlice.actions; 24 | export default counterSlice.reducer; 25 | -------------------------------------------------------------------------------- /chapter11/01_redux_counter/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter11/01_redux_counter/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter11/01_redux_counter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter11/02_zustand_counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "web-vitals": "^1.0.1", 18 | "zustand": "^3.6.5" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter11/02_zustand_counter/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter11/02_zustand_counter/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Counter } from "./Counter"; 2 | 3 | const App = () => ( 4 |
5 | 6 | 7 |
8 | ); 9 | 10 | export default App; 11 | -------------------------------------------------------------------------------- /chapter11/02_zustand_counter/src/Counter.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from "./store"; 2 | 3 | export function Counter() { 4 | const count = useStore((state) => state.counter.value); 5 | const { increment, decrement } = useStore((state) => state.counterActions); 6 | return ( 7 |
8 |
9 | 10 | {count} 11 | 12 |
13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /chapter11/02_zustand_counter/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter11/02_zustand_counter/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter11/02_zustand_counter/src/store.ts: -------------------------------------------------------------------------------- 1 | import create from "zustand"; 2 | 3 | type State = { 4 | counter: { 5 | value: number; 6 | }; 7 | counterActions: { 8 | increment: () => void; 9 | decrement: () => void; 10 | incrementByAmount: (amount: number) => void; 11 | }; 12 | }; 13 | 14 | export const useStore = create((set) => ({ 15 | counter: { value: 0 }, 16 | counterActions: { 17 | increment: () => 18 | set((state) => ({ counter: { value: state.counter.value + 1 } })), 19 | decrement: () => 20 | set((state) => ({ counter: { value: state.counter.value - 1 } })), 21 | incrementByAmount: (amount: number) => 22 | set((state) => ({ counter: { value: state.counter.value + amount } })), 23 | }, 24 | })); 25 | -------------------------------------------------------------------------------- /chapter11/02_zustand_counter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter11/03_recoil_charcounter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "recoil": "^0.5.2", 17 | "typescript": "^4.1.2", 18 | "web-vitals": "^1.0.1" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter11/03_recoil_charcounter/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter11/03_recoil_charcounter/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | RecoilRoot, 3 | atom, 4 | selector, 5 | useRecoilState, 6 | useRecoilValue, 7 | } from "recoil"; 8 | 9 | const textState = atom({ 10 | key: "textState", 11 | default: "", 12 | }); 13 | 14 | const TextInput = () => { 15 | const [text, setText] = useRecoilState(textState); 16 | return ( 17 |
18 | { 22 | setText(event.target.value); 23 | }} 24 | /> 25 |
26 | Echo: {text} 27 |
28 | ); 29 | }; 30 | 31 | const charCountState = selector({ 32 | key: "charCountState", 33 | get: ({ get }) => get(textState).length, 34 | }); 35 | 36 | const CharacterCount = () => { 37 | const count = useRecoilValue(charCountState); 38 | return <>Character Count: {count}; 39 | }; 40 | 41 | const CharacterCounter = () => ( 42 |
43 | 44 | 45 |
46 | ); 47 | 48 | const App = () => ( 49 | 50 | 51 | 52 | ); 53 | 54 | export default App; 55 | -------------------------------------------------------------------------------- /chapter11/03_recoil_charcounter/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter11/03_recoil_charcounter/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter11/03_recoil_charcounter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter11/04_jotai_charcounter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "jotai": "^1.4.6", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "react-scripts": "4.0.3", 17 | "typescript": "^4.1.2", 18 | "web-vitals": "^1.0.1" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter11/04_jotai_charcounter/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter11/04_jotai_charcounter/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { atom, useAtom } from "jotai"; 2 | 3 | const textAtom = atom(""); 4 | 5 | const TextInput = () => { 6 | const [text, setText] = useAtom(textAtom); 7 | return ( 8 |
9 | { 13 | setText(event.target.value); 14 | }} 15 | /> 16 |
17 | Echo: {text} 18 |
19 | ); 20 | }; 21 | 22 | const charCountAtom = atom((get) => get(textAtom).length); 23 | 24 | const CharacterCount = () => { 25 | const [count] = useAtom(charCountAtom); 26 | return <>Character Count: {count}; 27 | }; 28 | 29 | const CharacterCounter = () => ( 30 |
31 | 32 | 33 |
34 | ); 35 | 36 | const App = () => ( 37 | <> 38 | 39 | 40 | ); 41 | 42 | export default App; 43 | -------------------------------------------------------------------------------- /chapter11/04_jotai_charcounter/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter11/04_jotai_charcounter/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter11/04_jotai_charcounter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter11/05_mobx_timer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "mobx": "^6.3.8", 14 | "mobx-react": "^7.2.1", 15 | "react": "^17.0.2", 16 | "react-dom": "^17.0.2", 17 | "react-scripts": "4.0.3", 18 | "typescript": "^4.1.2", 19 | "web-vitals": "^1.0.1" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /chapter11/05_mobx_timer/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter11/05_mobx_timer/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { makeAutoObservable } from "mobx"; 2 | import { observer } from "mobx-react"; 3 | 4 | class Timer { 5 | secondsPassed = 0; 6 | constructor() { 7 | makeAutoObservable(this); 8 | } 9 | increase() { 10 | this.secondsPassed += 1; 11 | } 12 | reset() { 13 | this.secondsPassed = 0; 14 | } 15 | } 16 | 17 | const myTimer = new Timer(); 18 | 19 | setInterval(() => { 20 | myTimer.increase(); 21 | }, 1000); 22 | 23 | const TimerView = observer(({ timer }: { timer: Timer }) => ( 24 | 27 | )); 28 | 29 | const App = () => ( 30 | <> 31 | 32 | 33 | ); 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /chapter11/05_mobx_timer/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter11/05_mobx_timer/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter11/05_mobx_timer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter11/06_valtio_timer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.1.2", 17 | "valtio": "^1.2.7", 18 | "web-vitals": "^1.0.1" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter11/06_valtio_timer/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter11/06_valtio_timer/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { proxy, useSnapshot } from "valtio"; 2 | 3 | const myTimer = proxy({ 4 | secondsPassed: 0, 5 | increase: () => { 6 | myTimer.secondsPassed += 1; 7 | }, 8 | reset: () => { 9 | myTimer.secondsPassed = 0; 10 | }, 11 | }); 12 | 13 | setInterval(() => { 14 | myTimer.increase(); 15 | }, 1000); 16 | 17 | const TimerView = ({ timer }: { timer: typeof myTimer }) => { 18 | const snap = useSnapshot(timer); 19 | return ( 20 | 23 | ); 24 | }; 25 | 26 | const App = () => ( 27 | <> 28 | 29 | 30 | ); 31 | 32 | export default App; 33 | -------------------------------------------------------------------------------- /chapter11/06_valtio_timer/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /chapter11/06_valtio_timer/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter11/06_valtio_timer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/msmrh/27b3993aa81d9c034ac72ca435205e6cb14a9ff1/cover.jpg --------------------------------------------------------------------------------