Slide 1

Slide 1 text

OFF THE HOOK REFACTORING S

Slide 2

Slide 2 text

YOU MIGHT HAVE HEARD OF REACT https://evilmartians.com/chronicles/optimizing-react-virtual-dom-explained

Slide 3

Slide 3 text

1. DECLARATIVE, 2. COMPONENT-BASED, 3. LEARN ONCE, WRITE EVERYWHERE react docs A JAVASCRIPT LIBRARY FOR BUILDING USER INTERFACES

Slide 4

Slide 4 text

react docs A JAVASCRIPT LIBRARY FOR BUILDING USER INTERFACES 1. DECLARATIVE, 2. COMPONENT-BASED, 3. LEARN ONCE, WRITE EVERYWHERE

Slide 5

Slide 5 text

STATE PROPS https://thenounproject.com/indygo/collection/hand-drawn-arrows-4/

Slide 6

Slide 6 text

Type a quote here. A SIMPLE FUNCTION COMPONENT const HelloMessage = (props) => (
Hello {props.name}
); ReactDOM.render( , document.getElementById('hello-example') );

Slide 7

Slide 7 text

Type a quote here. A SIMPLE CLASS COMPONENT class HelloMessage extends React.Component { render() { return (
Hello {this.props.name}
); } } ReactDOM.render( , document.getElementById('hello-example') );

Slide 8

Slide 8 text

NEITHER MODEL REALLY CAPTURES REACT.

Slide 9

Slide 9 text

https://dan.church

Slide 10

Slide 10 text

dan abramov WHAT'S A COMPONENT WHY ARE THESE MODELS INSUFFICIENT TO DESCRIBE REACT?

Slide 11

Slide 11 text

dan abramov WHAT'S A COMPONENT “PURE FUNCTION” MODEL DOESN’T DESCRIBE LOCAL STATE WHICH IS AN ESSENTIAL REACT FEATURE.

Slide 12

Slide 12 text

dan abramov WHAT'S A COMPONENT “CLASS” MODEL DOESN’T EXPLAIN PURE-ISH RENDER, DISAWOVING INHERITANCE, LACK OF DIRECT INSTANTIATION, AND “RECEIVING” PROPS.

Slide 13

Slide 13 text

@cedmax EMAIL: NAME marco WEBSITE cedmax.com TWITTER PRONOUNS: HE/HIM

Slide 14

Slide 14 text

WE'RE ARE GOING TO TALK ABOUT COLOURS

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

Type a quote here. export default class App extends Component { state = { colors: originalList, currentFilter: "", currentSortBy: "name", style: { ...constants, color: "black", background: "white" }, }; sortBy = sortBy => { this.setState({ currentSortBy: sortBy, colors: sorters[sortBy](this.state.colors), }); }; filter = currentFilter=> { const colors = getFilteredColours(originalList, currentFilter); this.setState({ currentFilter, colors }); }; COMPONENT STRUCTURE

Slide 20

Slide 20 text

Type a quote here. onColorChange = hex => { this.setState({ style: { ...this.state.style, background: hex, color: getMostReadable(hex), }, }); }; return ( ); }; COMPONENT STRUCTURE

Slide 21

Slide 21 text

LET'S CODE

Slide 22

Slide 22 text

HOOKS

Slide 23

Slide 23 text

1. OPT-IN 2. 100% BACKWARDS-COMPATIBLE 3. AVAILABLE NOW 
 HOOKS WERE RELEASED WITH REACT V16.8.0. react docs A JAVASCRIPT LIBRARY FOR BUILDING USER INTERFACES

Slide 24

Slide 24 text

USESTATE

Slide 25

Slide 25 text

const [colors, setColors] = useState(originalList); USESTATE ARRAY DEFAULT VALUE VALUE FUNCTION TO SET THE STATE

Slide 26

Slide 26 text

const [colors, setColors] = useState(originalList); setColors(sortingFunction); CAN TAKE A FUNCTION... ARRAY DEFAULT VALUE VALUE FUNCTION TO SET THE STATE USESTATE

Slide 27

Slide 27 text

const [colors, setColors] = useState(originalList); setColors(sortingFunction); CAN TAKE A FUNCTION... ... AND SO DOES USE STATE ARRAY DEFAULT VALUE VALUE FUNCTION TO SET THE STATE USESTATE

Slide 28

Slide 28 text

const [colors, setColors] = useState(originalList); setColors(sortingFunction); setColors(sortingFunction(colors)); THEY ARE EXACTLY THE SAME ARRAY DEFAULT VALUE VALUE FUNCTION TO SET THE STATE CAN TAKE A FUNCTION... ... AND SO DOES USE STATE USESTATE

Slide 29

Slide 29 text

setCurrentFilter(currentFilter); setColors(colors); COMPONENTS UPDATES GET ENQUEUED USESTATE

Slide 30

Slide 30 text

USECALLBACK

Slide 31

Slide 31 text

const sortBy = useCallback(e => { const { value: sortBy } = e.target; const sortingFunction = sorters[sortBy]; setColors(sortingFunction); }, []); USECALLBACK WRAPS A FUNCTION RETURNING
 A MEMOIZED VERSION

Slide 32

Slide 32 text

USEREDUCER

Slide 33

Slide 33 text

export default () => { const [colors, setColors] = useState(originalList); const [currentFilter, setCurrentFilter] = useState(""); const [currentSortBy, setCurrentSortBy] = useState("name"); const [style, setStyle] = useState({ ...constants, color: "black", background: "white", }); const sortBy = useCallback(sortBy => { const sortingFunction = sorters[sortBy]; setCurrentSortBy(sortBy); setColors(sortingFunction); }, []); const filter = useCallback(currentFilter => { const colors = getFilteredColors(originalList, currentFilter); setCurrentFilter(currentFilter); setColors(colors); }, []); STILL QUITE BUSY

Slide 34

Slide 34 text

const onColorChange = useCallback(hex => { setStyle({ ...style, background: hex, color: getMostReadable(hex), }); }, []); return ( ); }; STILL QUITE BUSY

Slide 35

Slide 35 text

const [state, dispatch] = useReducer(reducer, defaultState); USEREDUCER ARRAY DEFAULT VALUE VALUE REDUCERS

Slide 36

Slide 36 text

export default (state, { type, payload }) => { switch (type) { case "filter": return { ...state, currentFilter: payload, colors: getFilteredColors(state.allColors, payload), }; case "sort": return { ...state, currentSortBy: payload, colors: sorters[payload](state.colors), }; case "change": return { ...state, style: { ...state.style, background: payload, color: getMostReadable(payload), }, }; default: return state; } }; USEREDUCER

Slide 37

Slide 37 text

const [state, dispatch] = useReducer(reducer, defaultState); const emit = useCallback((type, payload) => dispatch({type, payload}), []); const sortBy = useCallback(sortBy => emit("sort", sortBy), []); const filter = useCallback(filter => emit("filter", filter), []); const onColorChange = useCallback(hex => emit("change", hex), []); USEREDUCER

Slide 38

Slide 38 text

const [state, dispatch] = useReducer(reducer, defaultState); const emit = useCallback((type, payload) => dispatch({type, payload}), []); const sortBy = useCallback(sortBy => emit("sort", sortBy), []); const filter = useCallback(filter => emit("filter", filter), []); const onColorChange = useCallback(hex => emit("change", hex), []); USEREDUCER

Slide 39

Slide 39 text

export default () => { const [state, dispatch] = useReducer(reducers, defaultState); const emit = useCallback((type, payload) => dispatch({ type, payload }), []); const sortBy = useCallback(sortBy => emit("sort", sortBy), []); const filter = useCallback(filter => emit("filter", filter), []); const onColorChange = useCallback(hex => emit("change", hex), []); return ( ); }; USEREDUCER

Slide 40

Slide 40 text

USEEFFECT

Slide 41

Slide 41 text

ACCEPTS A FUNCTION THAT CONTAINS IMPERATIVE, POSSIBLY EFFECTFUL CODE. react docs USEEFFECT

Slide 42

Slide 42 text

THINK OF EFFECTS AS AN ESCAPE HATCH FROM REACT’S PURELY FUNCTIONAL WORLD INTO THE IMPERATIVE WORLD. react docs USEEFFECT

Slide 43

Slide 43 text

NETWORK (DATA FETCHING...), DOM OR WINDOW (TITLE UPDATES, SUBSCRIBE TO WINDOW RESIZE OR MOUSE EVENTS, ACCESS LOCAL STORAGE), LOGGING (ANALYTICS...)

Slide 44

Slide 44 text

FETCHING DATA useEffect(() => { const fetchData = async () => { const { data } = await axios.get(`/api/colors/${props.id}`); setColor(data); }; fetchData(); }, [props.id]);

Slide 45

Slide 45 text

FETCHING DATA NOT BEING ABLE TO USE AN ASYNC FUNCTION WAS QUITE A GOTCHA FOR ME useEffect(() => { const fetchData = async () => { const { data } = await axios.get(`/api/colors/${props.id}`); setColor(data); }; fetchData(); }, [props.id]);

Slide 46

Slide 46 text

FETCHING DATA NOT BEING ABLE TO USE AN ASYNC FUNCTION WAS QUITE A GOTCHA FOR ME LIST OF DEPENDENCIES useEffect(() => { const fetchData = async () => { const { data } = await axios.get(`/api/colors/${props.id}`); setColor(data); }; fetchData(); }, [props.id]);

Slide 47

Slide 47 text

REACT LIFECYCLES WITH HOOKS //componentDidMount useEffect(() => console.log('mounted'), []); //componentDidUnmount useEffect(() => () => { console.log('will unmount'); }, []); THE EMPTY DEPENDENCY LIST
 GUARANTEES IT'S EXECUTED ONLY ONCE NO MATTER THE NUMBER OF RE-RENDERS

Slide 48

Slide 48 text

//componentDidMount useEffect(() => console.log('mounted'), []); //componentDidUnmount useEffect(() => () => { console.log('will unmount'); }, []); THE EMPTY DEPENDENCY LIST
 GUARANTEES IT'S EXECUTED ONLY ONCE NO MATTER THE NUMBER OF RE-RENDERS IF THE CALLBACK RETURNS A
 FUNCTION, IT WILL BE CALLED BEFORE THE COMPONENT IS REMOVED FROM THE UI. REACT LIFECYCLES WITH HOOKS

Slide 49

Slide 49 text

THE QUESTION IS NOT "WHEN DOES THIS EFFECT RUN" 
 
 THE QUESTION IS "WITH WHICH STATE DOES THIS EFFECT SYNCHRONIZE WITH" ryan florence REACT LIFECYCLES WITH HOOKS

Slide 50

Slide 50 text

useEffect(() => console.log('mounted')); useEffect(() => console.log(''), []); useEffect( () => console.log(`fetch ${props.id}`), [props.id] ); WHAT STATE THE EFFECT SYNCS TO? ALL STATE CHANGES

Slide 51

Slide 51 text

useEffect(() => console.log('mounted')); useEffect(() => console.log(''), []); useEffect(
 () => console.log(`fetch ${props.id}`),
 [props.id]
 ); NO STATE CHANGES WHAT STATE THE EFFECT SYNCS TO? ALL STATE CHANGES

Slide 52

Slide 52 text

useEffect(() => console.log('mounted')); useEffect(() => console.log(''), []); useEffect( () => console.log(`fetch ${props.id}`), [props.id] ); PROP ID CHANGES WHAT STATE THE EFFECT SYNCS TO? NO STATE CHANGES ALL STATE CHANGES

Slide 53

Slide 53 text

UPDATE THE URL WITH USEEFFECT

Slide 54

Slide 54 text

const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { const { currentFilter, currentSortBy } = data; // from the URL sortBy(currentSortBy || defaultSortBy); filter(currentFilter || defaultFilter); }); setUrlRestored(true); } else { objectToHistory({ currentSortBy, currentFilter, }); } }, [currentSortBy, currentFilter]); OUR DEPENDENCIES ARE THE VALUES
 WE WANT TO PERSIST IN THE URL MATCH URL TO STATE

Slide 55

Slide 55 text

const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { const { currentFilter, currentSortBy } = data; // from the URL sortBy(currentSortBy || defaultSortBy); filter(currentFilter || defaultFilter); }); setUrlRestored(true); } else { objectToHistory({ currentSortBy, currentFilter, }); } }, [currentSortBy, currentFilter]); ON FIRST LOAD WE NEED TO RESTORE
 THE STATE FROM THE URL AND SUBSCRIBE 
 TO THE HISTORY EVENTS OUR DEPENDENCIES ARE THE VALUES
 WE WANT TO PERSIST IN THE URL MATCH URL TO STATE

Slide 56

Slide 56 text

const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { const { currentFilter, currentSortBy } = data; // from the URL sortBy(currentSortBy || defaultSortBy); filter(currentFilter || defaultFilter); }); setUrlRestored(true); } else { objectToHistory({ currentSortBy, currentFilter, }); } }, [currentSortBy, currentFilter]); OUR DEPENDENCIES ARE THE VALUES
 WE WANT TO PERSIST IN THE URL ON FIRST LOAD WE NEED TO RESTORE
 THE STATE FROM THE URL AND SUBSCRIBE 
 TO THE HISTORY EVENTS MATCH URL TO STATE

Slide 57

Slide 57 text

export const listenToHistory = callback => { const getQs = () => qs.parse(window.location.search); window.addEventListener("popstate", () => callback(getQs())); callback(getQs()); }; 
 listenToHistory(data => { const { currentFilter, currentSortBy } = data; // from the URL sortBy(currentSortBy || defaultSortBy); filter(currentFilter || defaultFilter); }); THE CALLBACK GETS INVOKED IMMEDIATELY AND IN RESPONSE OF ANY POPSTATE EVENT WITH THE PARAMETERS IN THE QUERYSTRING MATCH URL TO STATE

Slide 58

Slide 58 text

export const listenToHistory = callback => { const getQs = () => qs.parse(window.location.search); window.addEventListener("popstate", () => callback(getQs())); callback(getQs()); }; listenToHistory(data => { const { currentFilter, currentSortBy } = data; // from the URL sortBy(currentSortBy || defaultSortBy); filter(currentFilter || defaultFilter); }); IN THE CALLBACK WE SET THE STATE FOR ANY
 CORRESPONDING PARAMETER IN THE URL THE CALLBACK GETS INVOKED IMMEDIATELY AND IN RESPONSE OF ANY POPSTATE EVENT WITH THE PARAMETERS IN THE QUERYSTRING MATCH URL TO STATE

Slide 59

Slide 59 text

const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { const { currentFilter, currentSortBy } = data; // from the URL sortBy(currentSortBy || defaultSortBy); filter(currentFilter || defaultFilter); }); setUrlRestored(true); } else { objectToHistory({ currentSortBy, currentFilter, }); } }, [currentSortBy, currentFilter]); OUR DEPENDENCIES ARE THE VALUES
 WE WANT TO PERSIST IN THE URL ON FIRST LOAD WE NEED TO RESTORE
 THE STATE FROM THE URL AND SUBSCRIBE 
 TO THE HISTORY EVENTS MATCH URL TO STATE

Slide 60

Slide 60 text

ON FIRST LOAD WE NEED TO RESTORE
 THE STATE FROM THE URL AND SUBSCRIBE 
 TO THE HISTORY EVENTS const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { const { currentFilter, currentSortBy } = data; // from the URL sortBy(currentSortBy || defaultSortBy); filter(currentFilter || defaultFilter); }); setUrlRestored(true); } else { objectToHistory({ currentSortBy, currentFilter, }); } }, [currentSortBy, currentFilter]); OUR DEPENDENCIES ARE THE VALUES
 WE WANT TO PERSIST IN THE URL MATCH URL TO STATE AND WE MAKE SURE IT HAPPENS 
 ONLY THE FIRST TIME

Slide 61

Slide 61 text

ON FIRST LOAD WE NEED TO RESTORE
 THE STATE FROM THE URL AND SUBSCRIBE 
 TO THE HISTORY EVENTS const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { const { currentFilter, currentSortBy } = data; // from the URL sortBy(currentSortBy || defaultSortBy); filter(currentFilter || defaultFilter); }); setUrlRestored(true); } else { objectToHistory({ currentSortBy, currentFilter, }); } }, [currentSortBy, currentFilter]); OUR DEPENDENCIES ARE THE VALUES
 WE WANT TO PERSIST IN THE URL MATCH URL TO STATE AND WE MAKE SURE IT HAPPENS 
 ONLY THE FIRST TIME AFTER THAT SETUP, WE CAN PUSH THE STATE CHANGES TO THE HISTORY

Slide 62

Slide 62 text

window.history.pushState({}, "", `?${qs.stringify(qsObj)}`); MATCH URL TO STATE

Slide 63

Slide 63 text

const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { const { currentFilter, currentSortBy } = data; // from the URL sortBy(currentSortBy || defaultSortBy); filter(currentFilter || defaultFilter); }); setUrlRestored(true); } else { objectToHistory({ currentSortBy, currentFilter, }); } }, [currentSortBy, currentFilter]); MATCH URL TO STATE

Slide 64

Slide 64 text

LET'S SEE IT WORKING

Slide 65

Slide 65 text

CUSTOM HOOKS

Slide 66

Slide 66 text

CUSTOM HOOK useQueryString( { currentSortBy, currentFilter }, { currentSortBy: sortBy, currentFilter: filter }, { currentSortBy: defaultSortBy, currentFilter: defaultFilter } );

Slide 67

Slide 67 text

useQueryString( { currentSortBy, currentFilter }, { currentSortBy: sortBy, currentFilter: filter }, { currentSortBy: defaultSortBy, currentFilter: defaultFilter } ); CUSTOM HOOK VALUES

Slide 68

Slide 68 text

useQueryString( { currentSortBy, currentFilter }, { currentSortBy: sortBy, currentFilter: filter }, { currentSortBy: defaultSortBy, currentFilter: defaultFilter } ); VALUES SETTERS, MATCHING 
 THE VALUES KEYS CUSTOM HOOK

Slide 69

Slide 69 text

useQueryString( { currentSortBy, currentFilter }, { currentSortBy: sortBy, currentFilter: filter }, { currentSortBy: defaultSortBy, currentFilter: defaultFilter } ); VALUES SETTERS, MATCHING 
 THE VALUES KEYS CUSTOM HOOK DEFAULT VALUES, MATCHING 
 THE VALUES KEYS

Slide 70

Slide 70 text

VALUES SETTERS useQueryString( [currentSortBy, sortBy, defaultSortBy], [currentFilter, filter, defaultFilter], ); CUSTOM HOOK useQueryString( { currentSortBy, currentFilter }, { currentSortBy: sortBy, currentFilter: filter }, { currentSortBy: defaultSortBy, currentFilter: defaultFilter } ); DEFAULTS

Slide 71

Slide 71 text

const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { const { currentFilter, currentSortBy } = data; // from the URL sortBy(currentSortBy || defaultSortBy); filter(currentFilter || defaultFilter); }); setUrlRestored(true); } else { objectToHistory({ currentSortBy, currentFilter, }); } }, [currentSortBy, currentFilter]); CUSTOM HOOK

Slide 72

Slide 72 text

export const useQueryString = (values, setters, defaults) => { const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { const { currentFilter, currentSortBy } = data; // from the URL sortBy(currentSortBy || defaultSortBy); filter(currentFilter || defaultFilter); }); setUrlRestored(true); } else { objectToHistory({ currentSortBy, currentFilter, }); } }, [currentSortBy, currentFilter]); }; CUSTOM HOOK

Slide 73

Slide 73 text

export const useQueryString = (values, setters, defaults) => { const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { const { currentFilter, currentSortBy } = data; // from the URL sortBy(currentSortBy || defaultSortBy); filter(currentFilter || defaultFilter); }); setUrlRestored(true); } else { objectToHistory({ currentSortBy, currentFilter, }); } }, [currentSortBy, currentFilter]); }; OUR DEPENDENCIES ARE THE VALUES
 WE WANT TO PERSIST IN THE URL CUSTOM HOOK

Slide 74

Slide 74 text

export const useQueryString = (values, setters, defaults) => { const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { const { currentFilter, currentSortBy } = data; // from the URL sortBy(currentSortBy || defaultSortBy); filter(currentFilter || defaultFilter); }); setUrlRestored(true); } else { objectToHistory({ currentSortBy, currentFilter, }); } }, Object.values(values)); }; OUR DEPENDENCIES ARE THE VALUES
 WE WANT TO PERSIST IN THE URL CUSTOM HOOK

Slide 75

Slide 75 text

export const useQueryString = (values, setters, defaults) => { const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { const { currentFilter, currentSortBy } = data; // from the URL sortBy(currentSortBy || defaultSortBy); filter(currentFilter || defaultFilter); }); setUrlRestored(true); } else { objectToHistory({ currentSortBy, currentFilter, }); } }, Object.values(values)); }; ON FIRST LOAD WE NEED TO RESTORE
 THE STATE FROM THE URL AND SUBSCRIBE 
 TO THE HISTORY EVENTS CUSTOM HOOK

Slide 76

Slide 76 text

export const useQueryString = (values, setters, defaults) => { const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { Object.keys(values).forEach(k => { const value = data[k]; // in the URL setters[k](value || defaults[k]); })}); setUrlRestored(true); } else { objectToHistory({ currentSortBy, currentFilter, }); } }, Object.values(values)); }; ON FIRST LOAD WE NEED TO RESTORE
 THE STATE FROM THE URL AND SUBSCRIBE 
 TO THE HISTORY EVENTS CUSTOM HOOK

Slide 77

Slide 77 text

export const useQueryString = (values, setters, defaults) => { const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { Object.keys(values).forEach(k => { const value = data[k]; // in the URL setters[k](value || defaults[k]); }) }); setUrlRestored(true); } else { objectToHistory({ currentSortBy, currentFilter, }); } }, Object.values(values)); }; CUSTOM HOOK

Slide 78

Slide 78 text

export const useQueryString = (values, setters, defaults) => { const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { Object.keys(values).forEach(k => { const value = data[k]; // in the URL setters[k](value || defaults[k]); }) }); setUrlRestored(true); } else { objectToHistory({ currentSortBy, currentFilter, }); } }, Object.values(values)); }; CUSTOM HOOK AFTER THAT SETUP, WE CAN PUSH THE STATE CHANGES TO THE HISTORY

Slide 79

Slide 79 text

export const useQueryString = (values, setters, defaults) => { const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { Object.keys(values).forEach(k => { const value = data[k]; // in the URL setters[k](value || defaults[k]); }) }); setUrlRestored(true); } else { objectToHistory(values); } }, Object.values(values)); }; CUSTOM HOOK AFTER THAT SETUP, WE CAN PUSH THE STATE CHANGES TO THE HISTORY

Slide 80

Slide 80 text

export const useQueryString = (values, setters, defaults) => { const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { Object.keys(values).forEach(k => { const value = data[k]; // in the URL setters[k](value || defaults[k]); }) }); setUrlRestored(true); } else { objectToHistory(values); } }, Object.values(values)); }; CUSTOM HOOK

Slide 81

Slide 81 text

export const useQueryString = (values, setters, defaults) => { const [urlRestored, setUrlRestored] = useState(false); useEffect(() => { if (!urlRestored) { listenToHistory(data => { Object.keys(values).forEach(k => { const value = data[k]; // in the URL setters[k](value || defaults[k]); }) }); setUrlRestored(true); } else { objectToHistory(values); } }, Object.values(values)); }; ABSTRACTION ABSTRACTION ABSTRACTION CUSTOM HOOK

Slide 82

Slide 82 text

LET'S MAKE SURE IT'S STILL WORKING

Slide 83

Slide 83 text

A STEP FORWARD

Slide 84

Slide 84 text

export const useQueryString = (values, setters, defaults) => { const [historyListener, setHistoryListener] = useState(null); useEffect(() => { if (!historyListener) { const listener = subscribeToHistory(data => { Object.keys(values).forEach(k => { const value = data[k]; setters[k](value || defaults[k]); }); }); setHistoryListener(() => listener); } else { objectToHistory(values); } return () => unsubscribeFromHistory(historyListener); }, Object.values(values)); }; CUSTOM HOOK WE COULD STORE THE LISTENER
 INSTEAD OF A BOOLEAN

Slide 85

Slide 85 text

export const useQueryString = (values, setters, defaults) => { const [historyListener, setHistoryListener] = useState(null); useEffect(() => { if (!historyListener) { const listener = subscribeToHistory(data => { Object.keys(values).forEach(k => { const value = data[k]; setters[k](value || defaults[k]); }); }); setHistoryListener(() => listener); } else { objectToHistory(values); } return () => unsubscribeFromHistory(historyListener); }, Object.values(values)); }; CUSTOM HOOK HAVE THE SUBSCRIBER RETURNING THE LISTENER WE COULD STORE THE LISTENER
 INSTEAD OF A BOOLEAN

Slide 86

Slide 86 text

export const useQueryString = (values, setters, defaults) => { const [historyListener, setHistoryListener] = useState(null); useEffect(() => { if (!historyListener) { const listener = subscribeToHistory(data => { Object.keys(values).forEach(k => { const value = data[k]; setters[k](value || defaults[k]); }); }); setHistoryListener(() => listener); } else { objectToHistory(values); } return () => unsubscribeFromHistory(historyListener); }, Object.values(values)); }; CUSTOM HOOK STORE THE LISTENER IN THE STATE WE COULD STORE THE LISTENER
 INSTEAD OF A BOOLEAN HAVE THE SUBSCRIBER RETURNING THE LISTENER

Slide 87

Slide 87 text

export const useQueryString = (values, setters, defaults) => { const [historyListener, setHistoryListener] = useState(null); useEffect(() => { if (!historyListener) { const listener = subscribeToHistory(data => { Object.keys(values).forEach(k => { const value = data[k]; setters[k](value || defaults[k]); }); }); setHistoryListener(() => listener); } else { objectToHistory(values); } return () => unsubscribeFromHistory(historyListener); }, Object.values(values)); }; CUSTOM HOOK STORE THE LISTENER IN THE STATE WE COULD STORE THE LISTENER
 INSTEAD OF A BOOLEAN RETURN A FUNCTION TO CLEAR THE EFFECT, UNSUBSCRIBING FROM THE HISTORY EVENTS HAVE THE SUBSCRIBER RETURNING THE LISTENER

Slide 88

Slide 88 text

export const useQueryString = (values, setters, defaults) => { const [historyListener, setHistoryListener] = useState(null); useEffect(() => { if (!historyListener) { const listener = subscribeToHistory(data => { Object.keys(values).forEach(k => { const value = data[k]; setters[k](value || defaults[k]); }); }); setHistoryListener(() => listener); } else { objectToHistory(values); } return () => unsubscribeFromHistory(historyListener); }, Object.values(values)); }; CUSTOM HOOK

Slide 89

Slide 89 text

export default () => { const [state, dispatch] = useReducer(reducers, defaultState); const emit = useCallback((type, payload) => dispatch({ type, payload }), []); const sortBy = useCallback(sortBy => emit("sort", sortBy), []); const filter = useCallback(filter => emit("filter", filter), []); const onColorChange = useCallback(hex => emit("change", hex), []); useQueryString( { currentSortBy: state.currentSortBy, currentFilter: state.currentFilter }, { currentSortBy: sortBy, currentFilter: filter }, defaultState ); return ( ); }; USEREDUCER

Slide 90

Slide 90 text

export const useQueryString = (values, setters, defaults) => { const [historyListener, setHistoryListener] = useState(null); useEffect(() => { if (!historyListener) { const listener = subscribeToHistory(data => { Object.keys(values).forEach(k => { const value = data[k]; setters[k](value || defaults[k]); }); }); setHistoryListener(() => listener); } else { objectToHistory(values); } return () => unsubscribeFromHistory(historyListener); }, Object.values(values)); }; CUSTOM HOOK

Slide 91

Slide 91 text

CUSTOM HOOKS
 TESTING

Slide 92

Slide 92 text

export const useQueryString = (values, setters, defaults) => { const [historyListener, setHistoryListener] = useState(null); useEffect(() => { if (!historyListener) { const listener = subscribeToHistory(data => { Object.keys(values).forEach(k => { const value = data[k]; setters[k](value || defaults[k]); }); }); setHistoryListener(() => listener); } else { objectToHistory(values); } return () => unsubscribeFromHistory(historyListener); }, Object.values(values)); }; TESTING

Slide 93

Slide 93 text

export const useQueryString = (values, setters, defaults) => { const [historyListener, setHistoryListener] = useState(null); useEffect(() => { if (!historyListener) { const listener = subscribeToHistory(data => { Object.keys(values).forEach(k => { const value = data[k]; setters[k](value || defaults[k]); }); }); setHistoryListener(() => listener); } else { objectToHistory(values); } return () => unsubscribeFromHistory(historyListener); }, Object.values(values)); }; TESTING

Slide 94

Slide 94 text

jest.mock("./utils", () => ({ objectToHistory: jest.fn(), subscribeToHistory: jest.fn(() => "listener function"), unsubscribeFromHistory: jest.fn(), }));
 
 const stateSetter = jest.fn(); const Component = ({ value }) => { useQueryString( { value }, { value: stateSetter }, { value: "defaultValue" } ); return null; }; TESTING

Slide 95

Slide 95 text

jest.mock("./utils", () => ({ objectToHistory: jest.fn(), subscribeToHistory: jest.fn(() => "listener function"), unsubscribeFromHistory: jest.fn(), })); 
 const stateSetter = jest.fn(); const Component = ({ value }) => { useQueryString( { value }, { value: stateSetter }, { value: "defaultValue" } ); return null; }; TESTING

Slide 96

Slide 96 text

test("the first time: subscribe to history with the right callback", () => { mount(); expect(subscribeToHistory).toBeCalledTimes(1); const popStateListener = subscribeToHistory.mock.calls[0][0]; popStateListener({ value: "a" }); expect(stateSetter).toBeCalledWith("a"); callback({ value: "" }); expect(stateSetter).toBeCalledWith("defaultValue"); }); TESTING

Slide 97

Slide 97 text

test("the first time: subscribe to history with the right callback", () => { mount(); expect(subscribeToHistory).toBeCalledTimes(1); const popStateListener = subscribeToHistory.mock.calls[0][0]; popStateListener({ value: "b" }); expect(stateSetter).toBeCalledWith("b"); callback({ value: "" }); expect(stateSetter).toBeCalledWith("defaultValue"); }); TESTING

Slide 98

Slide 98 text

test("the first time: subscribe to history with the right callback", () => { mount(); expect(subscribeToHistory).toBeCalledTimes(1); const popStateListener = subscribeToHistory.mock.calls[0][0]; popStateListener({ value: "b" }); expect(stateSetter).toBeCalledWith("b"); callback({ value: "" }); expect(stateSetter).toBeCalledWith("defaultValue"); }); TESTING

Slide 99

Slide 99 text

const wrapper = mount(); test("further executions should invoke objectToHistory", () => { wrapper.setProps({ value: "b" }); expect(subscribeToHistory).toBeCalledTimes(0); expect(objectToHistory).toBeCalledTimes(1); }); test("further executions with the same props should do nothing", () => { wrapper.setProps({ value: "b" }); expect(subscribeToHistory).toBeCalledTimes(0); expect(objectToHistory).toBeCalledTimes(0); }); test("unmounting should invoke unsubscribeFromHistory", () => { wrapper.unmount(); expect(unsubscribeFromHistory).toBeCalledTimes(1); expect(unsubscribeFromHistory).toBeCalledWith("listener function"); }); TESTING

Slide 100

Slide 100 text

const wrapper = mount(); test("further executions should invoke objectToHistory", () => { wrapper.setProps({ value: "b" }); expect(subscribeToHistory).toBeCalledTimes(0); expect(objectToHistory).toBeCalledTimes(1); }); test("further executions with the same props should do nothing", () => { wrapper.setProps({ value: "b" }); expect(subscribeToHistory).toBeCalledTimes(0); expect(objectToHistory).toBeCalledTimes(0); }); test("unmounting should invoke unsubscribeFromHistory", () => { wrapper.unmount(); expect(unsubscribeFromHistory).toBeCalledTimes(1); expect(unsubscribeFromHistory).toBeCalledWith("listener function"); }); TESTING

Slide 101

Slide 101 text

const wrapper = mount(); test("further executions should invoke objectToHistory", () => { wrapper.setProps({ value: "b" }); expect(subscribeToHistory).toBeCalledTimes(0); expect(objectToHistory).toBeCalledTimes(1); }); test("further executions with the same props should do nothing", () => { wrapper.setProps({ value: "b" }); expect(subscribeToHistory).toBeCalledTimes(0); expect(objectToHistory).toBeCalledTimes(0); }); test("unmounting should invoke unsubscribeFromHistory", () => { wrapper.unmount(); expect(unsubscribeFromHistory).toBeCalledTimes(1); expect(unsubscribeFromHistory).toBeCalledWith("listener function"); }); TESTING

Slide 102

Slide 102 text

FAQS

Slide 103

Slide 103 text

WHY HOOKS AGAIN?

Slide 104

Slide 104 text

THEY PROVIDE A BETTER MENTAL MODEL OF WHAT A COMPONENT IS. WHY HOOKS AGAIN?

Slide 105

Slide 105 text

WITHOUT HOOKS: 46.15KB
 WITH HOOKS: 45.69KB ~0.5KB REMOVING 1 CLASS WHY HOOKS AGAIN?

Slide 106

Slide 106 text

ARE THEY STABLE?

Slide 107

Slide 107 text

YES AND NO: THE BASIC ONES PROBABLY YES, BUT THEY MIGHT EVOLVE A BIT ARE THEY STABLE?

Slide 108

Slide 108 text

ARE THEY STABLE? INTENTIONALLY UNDERSPECIFYING DEPENDENCIES PASSED TO `USEEFFECT`/`USEMEMO` IS THE `ANY` OF REACT HOOKS. YOU THINK YOU'RE BEING CLEVER BY PASSING AN EMPTY ARRAY, BUT YOU'RE PROBABLY WRONG. INTENTIONALLY UNDERSPECIFYING DEPENDENCIES PASSED TO `USEEFFECT`/`USEMEMO` IS THE `ANY` OF REACT HOOKS. YOU THINK YOU'RE BEING CLEVER BY PASSING AN EMPTY ARRAY, BUT YOU'RE PROBABLY WRONG.

Slide 109

Slide 109 text

ARE THEY STABLE? INTENTIONALLY UNDERSPECIFYING DEPENDENCIES PASSED TO `USEEFFECT`/`USEMEMO` IS THE `ANY` OF REACT HOOKS. YOU THINK YOU'RE BEING CLEVER BY PASSING AN EMPTY ARRAY, BUT YOU'RE PROBABLY WRONG. INTENTIONALLY UNDERSPECIFYING DEPENDENCIES PASSED TO `USEEFFECT`/`USEMEMO` IS THE `ANY` OF REACT HOOKS. YOU THINK YOU'RE BEING CLEVER BY PASSING AN EMPTY ARRAY, BUT YOU'RE PROBABLY WRONG.

Slide 110

Slide 110 text

ARE THERE MORE HOOKS?

Slide 111

Slide 111 text

YES! A FEW MORE COME WITH REACT & CUSTOM ONES, COMMUNITY DRIVEN ARE THERE MORE HOOKS?

Slide 112

Slide 112 text

SHOULD I START USING THEM NOW?

Slide 113

Slide 113 text

UP TO YOU, BUT THEY ARE AVAILABLE TO USE SINCE REACT V16.8.0 SHOULD I START USING THEM NOW?

Slide 114

Slide 114 text

ARE CLASSES DISAPPEARING?

Slide 115

Slide 115 text

NOT IN THE FORESEEABLE FUTURE: THE REACT TEAM WAS CLEAR ABOUT IT ARE CLASSES DISAPPEARING?

Slide 116

Slide 116 text

WHAT'S THE MOST IMPORTANT TAKE AWAY?

Slide 117

Slide 117 text

WHAT'S THE MOST IMPORTANT TAKE AWAY?

Slide 118

Slide 118 text

colours.dsgn.it
 github.com/cedmax/colours overreacted.io
 dan abramov's blog usehooks.com
 code examples hooks.guide
 collection of React hooks reactjs.com
 official documentation [email protected] http://cedmax.com @cedmax