├── README.md └── hooks.js /README.md: -------------------------------------------------------------------------------- 1 | ## Composing Behavior in React or Why React Hooks are Awesome 2 | 3 | Abstract: Explore how React hooks use regular JavaScript function composition to model complex state and behavior. 4 | 5 | Where: React Loop 2019, Chicago, IL 6 | 7 | When: June 21, 2019 8 | 9 | ### Video 10 | 11 |

12 | 13 | Composing Behavior in React or Why React Hooks are Awesome 14 | 15 |

16 | 17 | ### Repo 18 | 19 | The repo I used in the talk is here: https://github.com/ReactTraining/hooks-workshop 20 | 21 | ### Examples 22 | 23 | All of the examples I built in this talk are here: https://github.com/mjackson/react-loop-2019/blob/master/hooks.js 24 | 25 | ### Outline 26 | 27 | - Who am I? 28 | - What does "composition" mean? 29 | - Discrete behaviors 30 | - May be combined 31 | - Shared interface is the key 32 | - Example: CLI tools 33 | - What is "function composition"? 34 | - Shared interface is arguments + return value 35 | - Example: `add` function 36 | - React Hooks are functions with superpowers 37 | - They can manage state 38 | - They can participate in the lifecycle 39 | - They can receive implicit arguments (via context) 40 | - But the best feature of all? Composition! 41 | - They are **just functions**, after all 42 | - Custom Hooks 43 | - useLogging in \ (single behavior) 44 | - usePersistentState in \ (single state + behavior) 45 | - useUndo in \ (ref + state + behavior + method override) 46 | - Hooks as useReducer Middleware 47 | - useLogger in \ 48 | - useThunk in \ 49 | -------------------------------------------------------------------------------- /hooks.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from 'react'; 2 | 3 | export function useLogging(message) { 4 | useEffect(() => { 5 | console.log(message); 6 | }, [message]); 7 | } 8 | 9 | function getLocalStorageValue(key) { 10 | const val = localStorage.getItem(key); 11 | if (!val) return null; 12 | try { 13 | return JSON.parse(val); 14 | } catch (e) { 15 | return null; 16 | } 17 | } 18 | 19 | function setLocalStorage(key, value) { 20 | localStorage.setItem(key, JSON.stringify(value)); 21 | } 22 | 23 | export function usePersistentState(key, defaultState = '') { 24 | const [state, setState] = useState(getLocalStorageValue(key) || defaultState); 25 | 26 | useEffect(() => { 27 | setLocalStorage(key, state); 28 | }); 29 | 30 | return [state, setState]; 31 | } 32 | 33 | export function useUndo([state, setState]) { 34 | const history = useRef([state]); 35 | const [index, setIndex] = useState(0); 36 | 37 | function undo() { 38 | setIndex(Math.max(0, index - 1)); 39 | } 40 | function redo() { 41 | setIndex(Math.min(history.current.length - 1, index + 1)); 42 | } 43 | function newSetState(nextState) { 44 | const nextIndex = index + 1; 45 | // Truncate any future redos. 46 | history.current = history.current.slice(0, nextIndex); 47 | history.current[nextIndex] = nextState; 48 | setIndex(nextIndex); 49 | setState(nextState); 50 | } 51 | 52 | return [history.current[index], newSetState, undo, redo]; 53 | } 54 | 55 | export function useLogger([state, dispatch]) { 56 | const actionRef = useRef(); 57 | 58 | const newDispatchRef = useRef(action => { 59 | actionRef.current = action; 60 | dispatch(action); 61 | }); 62 | 63 | useEffect(() => { 64 | const action = actionRef.current; 65 | 66 | if (action) { 67 | console.group('Dispatch'); 68 | console.log('Action:', action); 69 | console.log('State:', state); 70 | console.groupEnd(); 71 | } 72 | }, [state]); 73 | 74 | return [state, newDispatchRef.current]; 75 | } 76 | 77 | export function useThunk([state, dispatch]) { 78 | const stateRef = useRef(); 79 | stateRef.current = state; 80 | 81 | const getStateRef = useRef(() => stateRef.current); 82 | 83 | const newDispatchRef = useRef(action => { 84 | if (typeof action === 'function') { 85 | action(newDispatchRef.current, getStateRef.current); 86 | } else { 87 | dispatch(action); 88 | } 89 | }); 90 | 91 | return [state, newDispatchRef.current]; 92 | } 93 | --------------------------------------------------------------------------------