├── 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 |
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 |
--------------------------------------------------------------------------------