PREACT HOOKS
HOW DO WE USE HOOKS
Junmin Liu
PART I
▸ Why do we need hooks
▸ What are hooks
PART II
▸ How do we use hooks
▸ When should you use or not use
hooks?
WHY DO WE NEED HOOKS?
Alex Chen
TEXT
HISTORICAL
CONTEXT
BEFORE REACT
+
DATA TEMPLATE
HTML
REACT/PREACT
HTML
JSX/HYPERTEXT
RE-RENDER
STATE
COMPONENT
INITIALIZATION
MOUTING
UPDATING
UNMUTING
Lifecycle
STATE MANAGEMENT
REDUX
STORE
PROBLEMS
HOW TO SHARE LOGIC
BETWEEN COMPONENTS?
Tony Lee
TEXT
REUSING LOGIC
HIGHER-ORDER COMPONENTS
▸ A function that takes a component
and returns a new component
▸ e.g. React-Redux’s connect function
<ComponentWhichImplementsLogic>
<ComponentWhichUsesLogic />
<ComponentWhichImplementsLogic>
ComponentWhichImplementsLogic(
ComponentWhichUsesLogic
)
withMousePosition(withClock(MyComponent))
withClock(MyComponent)
REUSING LOGIC
RENDER PROPS
▸ Using a prop whose value is a
function.
▸
<Clock
render={props => <MyComponent {...props} />}
/>
<Clock render={({time}) => (
<MousePosition render={({x, y}) => (
<MyComponent time={time} x={x} y={y} />
)} />
)} />
WRAPPER
HELL
GIANT COMPONENTS
Sophie Alpert
TEXT
GIANT COMPONENTS
▸ The logic is split across a lot of different lifecycle methods
▸ Duplicate code for different lifecycle methods
▸ Thousands of lines code
export default class Responsive extends Component {
constructor(props) {
super(props);
this.state = {};
this.syncState = this.syncState.bind(this);
}
syncState() {
const windowDimensions = Dimensions.get('window');
const screenDimensions = Dimensions.get('screen');
this.setState({
windowWidth: windowDimensions.width, windowHeight: windowDimensions.height,
screenWidth: screenDimensions.width, screenHeight: screenDimensions.height,
});
}
componentDidMount() {
this.syncState();
Dimensions.addEventListener('change', this.syncState);
}
componentWillUnmount() {
Dimensions.removeEventListener('change', this.syncState);
}
render() {
const renderChildren = this.props.children;
return renderChildren(this.state);
}
}
SHOULD I USE CLASS
COMPONENT OR FUNCTION
COMPONENT?
Rajalakshmi Somasundaram
TEXT
CONFUSING CLASSES
▸ Use class component to access state and life cycles
▸ Have to convert your function component to class
component if you add some state (Why not use class
component by default)
▸ Don’t forget `bind(this)` 😄
WHAT’S REACT HOOKS?
Agustin Quiroga
TEXT
Functions which can only be used from inside
Functional Components or Other Hooks
Named useXXX()
Use state, life cycles and other features
without writing a class
Hooks are highly re-usable
and independent for each component
HOOKS EXAMPLE
NO WRAPPER HELL
const MyComponent = () => {
const [time] = useClock();
const [x, y] = useMouseMove();
// do something with those values
return (
<>
<div>Current time: {time}</div>
<div>Mouse coordinates: {x},{y}</div>
</>
);
}
<Clock render={({time}) => (
<MousePosition render={({x, y}) => (
<MyComponent time={time} x={x} y={y} />
)} />
)} />
withMousePosition(withClock(MyComponent))
RENDER PROPS
HOC HOOKS
HOOKS EXAMPLE
NO GIANT COMPONENT
class FriendStatus extends React.Component {
constructor(props) {
super(props);
this.state = { isOnline: null };
this.handleStatusChange =
this.handleStatusChange.bind(this);
}
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
render() {
if (this.state.isOnline === null) {
return 'Loading...';
}
return this.state.isOnline ? 'Online' : 'Offline';
}
}
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
APIS ARE FREQUENTLY USED
▸ useState
▸ useEffect
▸ useContext
▸ useMemo / useCallback
▸ useRef
▸ useReducer
useState
▸ Manage states in your functional components
import { h, useState } from 'preact/hooks';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
useEffect
▸ componentDidMount + componentDidUpdate + componentWillUnmount
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id,
handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id,
handleStatusChange);
};
}, [props.friend.id]); // Only re-subscribe if
props.friend.id changes
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentDidUpdate(prevProps) {
// Unsubscribe from the previous friend.id
ChatAPI.unsubscribeFromFriendStatus(
prevProps.friend.id,
this.handleStatusChange
);
// Subscribe to the next friend.id
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
useContext
▸ Return React.createContext as a value, no more “Wrapper Hell”
const ThemeContext = createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (<div><ThemedButton /></div>);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
useMemo
▸ Cache expensive calculations to make the render faster
▸ Similar to reselect for redux
▸ Fuzzy Highlighter Example
export default function FuzzyHighlighter(props) {
const { query, children, color, style } = props;
function highlight(text, query) {
// calculate highlight text
}
const highlightedResult = useMemo(
() => highlight(children, query),
[query, children]
);
return (
<Text color={color} style={style}>
{highlightedResult}
</Text>
);
}
CACHE DEPENDENCY
RESULT CACHED
useCallback
▸ useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useRef
▸ useRef returns a mutable ref object. The returned object will persist for
the full lifetime of the component.
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text
input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the
input</button>
</>
);
}
class TextInputWithFocusButton extends Component {
constructor(props) {
super(props);
this.inputEl = null;
this.handleButtonClick =
this.handleButtonClick.bind(this);
this.handleRef = this.handleRef.bind(this);
}
handleButtonClick() {
this.inputEl.focus();
}
handleRef(elm) {
this.inputEl = elm;
}
return (
<>
<input ref={handleRef} type="text" />
<button onClick={this.handleButtonClick}>Focus</
button>
</>
);
}
useState
useMemo
useCallback
useRef
DATA HOOKS MEMOIZE HOOKS
useReducer
▸ useReducer + Context API = redux
▸ when you have complex state logic that involves multiple sub-values
or when the next state depends on the previous one.
▸ 《Use Hooks + Context, not React + Redux》
Q & A
Junmin Liu
PART I
HOW DO YOU USE HOOKS?
Yizhi Zhou
TEXT
useHash()
Example 1
TEXT
REQUIREMENT
▸ Show the dialog if the url hash is
#do-not-sell
▸ Hide the dialog if the urls hash is
not #do-not-sell
IMPLEMENT IT WITH CLASS COMPONENT
import React, { Component } from 'react';
export class DoNotSell extends Component {
constructor(props) {
super(props);
this.state = {
hash: window.location.hash
}
this.handleHashChange =
this.handleHashChange.bind(this);
}
componentDidMount() {
window.addEventListener('hashchange',
this.handleHashChange);
}
componentWillUnmount() {
window.removeEventListener('hashchange',
this.handleHashChange);
}
handleHashChange() {
this.setState({
hash: window.location.hash
})
}
handleClose() {
window.location.hash = '';
}
render() {
const open = this.state.hash === '#do-not-sell'
return (
<Dialog
open={open}
onClose={this.handleClose}
>
...
</Dialog>
);
}
}
TEXT
LOOKS GOOD SO FAR, BUT …
▸ It’s hard to reuse the logic
▸ Mix the logic with UI together
LET’S TRY CUSTOM HOOKS
Junmin Liu
TEXT
IMPLEMENT IT WITH HOOKS
export default function useHash(initialValue = null) {
// init state with current hash value
const [hash, setHash] = useState(window.location.hash);
// change the url hash
const setHashValue = value => {
window.location.hash = value ? `#${value}` : '';
};
useEffect(() => {
function handleHashChange() {
// update state value with current hash
setHash(window.location.hash);
}
window.addEventListener('hashchange', handleHashChange);
return function cleanup() {
window.removeEventListener('hashchange', handleHashChange);
};
});
return [hash, setHashValue];
}
export function DoNotSell() {
const [hash, setHash] = useHash();
const open = hash === '#do-not-sell'
return (
<Dialog
open={open}
onClose={() => setHash('')}
>
...
</Dialog>
);
}
TEXT
BENEFITS WE GOT
▸ Reuse the logic easily:
▸ lib/hooks/useHash
▸ Separate the UI and business logic
useSuggestions
Example2
TEXT
REQUIREMENT
▸ Type keyword
▸ Show autocomplete suggestions
I’M A BIG FAN OF REDUX!
I LOVE REDUX DEV TOOL!
Raji
TEXT
STORE
REDUCERS
ACTIONS API
VIEWSINPUT
SUGGESTIONS
LIST
STATE
DISPATCHER
MIDDLEWARES
searchReducer
suggestionsReducer
STORE
REDUCERS
ACTIONS API
VIEWSINPUT
SUGGESTIONS
LIST
STATE
DISPATCHER
MIDDLEWARES
searchReducer
suggestionsReducer
EVENT
onInput
STORE
REDUCERS
ACTIONS API
VIEWSINPUT
SUGGESTIONS
LIST
STATE
DISPATCHER
MIDDLEWARES
searchReducer
suggestionsReducer
REQ
A
{
type: 'SUGGESTS_REQUEST',
query: 'piz'
}
LOADING…
STORE
REDUCERS
ACTIONS LOADING…
VIEWSINPUT
SUGGESTIONS
LIST
STATE
DISPATCHER
MIDDLEWARES
searchReducer
suggestionsReducer
S
A
{
search: { query: '' },
suggestions: {}
}
STORE
REDUCERS
ACTIONS LOADING…
VIEWSINPUT
SUGGESTIONS
LIST
STATE
DISPATCHER
MIDDLEWARES
searchReducer
suggestionsReducer
S A
{
search: { query: '' },
suggestions: {}
}
{
type: 'SUGGESTS_REQUEST',
query: 'piz'
}
{
search: { query: 'piz' },
suggestions: {}
}
STORE
REDUCERS
ACTIONS LOADING…
VIEWSINPUT
SUGGESTIONS
LIST
STATE
DISPATCHER
MIDDLEWARES
searchReducer
suggestionsReducer
S A
{
search: { query: 'piz' },
suggestions: {
'piz': {
loading: true,
items: []
}
}
}
STORE
REDUCERS
ACTIONS LOADING…
VIEWSINPUT
SUGGESTIONS
LIST
STATE
DISPATCHER
MIDDLEWARES
searchReducer
suggestionsReducer
SUB
STORE
REDUCERS
ACTIONS LOADING…
VIEWSINPUT
SUGGESTIONS
LIST
STATE
DISPATCHER
MIDDLEWARES
searchReducer
suggestionsReducer
S A
STORE
REDUCERS
ACTIONS API
VIEWSINPUT
SUGGESTIONS
LIST
STATE
DISPATCHER
MIDDLEWARES
searchReducer
suggestionsReducer
RES
A
{
type: 'SUGGESTS_SUCCESS',
query: 'piz',
items: [
'pizza',
'jets pizza',
'nicks pizza'
]
}
STORE
REDUCERS
ACTIONS API
VIEWSINPUT
SUGGESTIONS
LIST
STATE
DISPATCHER
MIDDLEWARES
searchReducer
suggestionsReducer
S
A
{
search: { query: 'piz' },
suggestions: {
'piz': {
loading: true,
items: []
}
}
}
STORE
REDUCERS
ACTIONS API
VIEWSINPUT
SUGGESTIONS
LIST
STATE
DISPATCHER
MIDDLEWARES
searchReducer
suggestionsReducer
S A
{
search: { query: 'piz' },
suggestions: {
'piz': {
loading: false,
items: [
'pizza',
'jets pizza',
'nicks pizza'
]
}
}
}
STORE
REDUCERS
ACTIONS API
VIEWSINPUT
SUGGESTIONS
LIST
STATE
DISPATCHER
MIDDLEWARES
searchReducer
suggestionsReducer
{
search: { query: 'piz' },
suggestions: {
'piz': {
loading: false,
items: [
'pizza',
'jets pizza',
'nicks pizza'
]
}
}
}
S A
STORE
REDUCERS
ACTIONS API
VIEWSINPUT
SUGGESTIONS
LIST
STATE
DISPATCHER
MIDDLEWARES
searchReducer
suggestionsReducer
SUB
STORE
REDUCERS
ACTIONS API
VIEWSINPUT
SUGGESTIONS
LIST
STATE
DISPATCHER
MIDDLEWARES
searchReducer
suggestionsReducer
SUGGESTIONS
LIST
STATE
SUGGESTIONS
LIST
STATE
search suggestions
{
query: 'piz'
}
{
piz: {
loading: false,
items: [
'pizza',
'jets pizza',
'nicks pizza'
]
}
}
props
{
loading: false,
items: [
'pizza',
'jets pizza',
'nicks pizza'
]
}
WHY DO WE NEED REDUX FOR
SUGGESTIONS?
Yizhi Zhou
TEXT
TEXT
WHY DO WE NEED REDUX?
▸ Communication between
components
▸ e.g. Input ->
SuggestionsList
▸ Dealing with async
▸ e.g. Ajax call
▸ Redux Dev Tool
COULD WE GET RIDE OF
REDUX FOR SUGGETIONS?
Tony Lee
TEXT
TEXT
COMMUNICATION BETWEEN COMPONENTS
▸ Share a same parent component
▸ Using React Context
SUGGESTIONS
INPUT
SUGGESTIONS
LIST
TEXT
DEALING WITH ASYNC
▸ We can’t use async/await in a render function. 😢
let loading = false;
const suggestions = await fetchSuggestions(query);
loading = false;
TEXT
DEALING WITH ASYNC
▸ But we can use custom hooks. 😄
const [query, setQuery] = useState('');
const {loading, suggestions} = useSuggestions(query);
if (loading) {
return 'loading...'
}
return (
<ul>
{suggestions.map(renderSuggstion)}
</ul>
)
DEMO WITH CODESANDEBOX
Junmin Liu
TEXT
https://codesandbox.io/s/priceless-paper-hde2b
render
Functional component
useState
useSuggestions(query)
INPUT
SUGGESTIONS
LIST
query: “preact”
useSuggestions(query)
loadingMap: {}useState
useState suggestionsMap: {}
useEffect(() => {}, [query])
FETCH
loading: false,
suggestions: []
loading: false,
suggestions: []
render
Functional component
useState
useSuggestions(query)
INPUT
SUGGESTIONS
LIST
query: “preact”
useSuggestions(query)
loadingMap: {}useState
useState suggestionsMap: {}
useEffect(() => {}, [query])
FETCH
loading: true,
suggestions: []
API
loadingMap: {
preact: true
}
loading: true,
suggestions: []
render
Functional component
useState
useSuggestions(query)
INPUT
SUGGESTIONS
LIST
query: “preact”
useSuggestions(query)
useState
useState suggestionsMap: {}
useEffect(() => {}, [query])
FETCH
loading: false,
suggestions: […]
API
loadingMap: {
preact: true
}
loadingMap: {
preact: false
}
suggestionsMap: {
preact: […]
}loading: false,
suggestions: […]
render
Functional component
useState
useSuggestions(query)
INPUT
SUGGESTIONS
LIST
query: “preact”
useSuggestions(query)
loadingMap: {}useState
useState
suggestionsMap: {
preact: […]
}
useEffect(() => {}, [query])
FETCH
loading: false,
suggestions: []
API
loadingMap: {
preact: false
}
loading: false,
suggestions: []
query: “react”
render
Functional component
useState
useSuggestions(query)
INPUT
SUGGESTIONS
LIST
query: “react”
useSuggestions(query)
loadingMap: {
preact: true
}
useState
useState
suggestionsMap: {
preact: […]
}
useEffect(() => {}, [query])
FETCH
loading: true,
suggestions: []
API
loadingMap: {
preact: false,
react: true
}
loading: true,
suggestions: []
render
Functional component
useState
useSuggestions(query)
INPUT
SUGGESTIONS
LIST
query: “react”
useSuggestions(query)
useState
useState
suggestionsMap: {
preact: […]
}
useEffect(() => {}, [query])
FETCH
loading: false,
suggestions: […]
API
loadingMap: {
preact: false,
react: true
}
loadingMap: {
preact: false,
react: false
}
suggestionsMap: {
preact: […],
react: […]
}loading: false,
suggestions: […]
WHEN SHOULD YOU USE OR
NOT USE HOOKS?
Raji
TEXT
HOOKS VS REDUX
HOOKS
▸ Light
▸ New APIs for function
components
▸ useReducer + Context works
like redux
▸ React Dev Tool
REDUX
▸ Powerful
▸ State management
framework
▸ React-Redux has hooks API,
useSelector, useDispatch
▸ Redux Dev Tool
COMPONENT APP
HOOKS REDUX
HOOKS
+
CONTEXT
REDUX
Q & A
Junmin Liu
TEXT

How do we use hooks