@tanner linsley

You

JSconf

in

Ho

oks!

Custom

React

React

React Hook

Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React

React

React Hook

Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React Hooks!

React

Hook Tutorials

Videos

Courses

JS

Devs

You

Hooks

Tutorials

Talks

Courses

not

your

Mother's

React Hook

Tutorial

Fast

Code ++

Fun +++++

"use" +++++++

React Hooks Basics

πŸŽ‰ Functions πŸŽ‰

Classes

Classes

class ProfilePage extends React.Component {
  state = {
    count: 0,
  }
  render() {
    return (
      <>
        <p>Count: {count}</p>
        <button onClick={() => this.setState({ count: count + 1 })}>
          Increment
        </button>
      </>
    )
  }
}

State 🀨 this.state

function Example() {
  const [count, setCount] = React.useState(0);

  return (
    <>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </>
  );
}

useState 😁 or useReducer

class ProfilePage extends React.Component {
  state = {
    user: null,
  };
  componentDidMount() {
    this.fetchData(this.props.id);
  }
  componentDidUpdate(prevProps) {
    if (prevProps.id !== this.props.id) {
      this.fetchData(this.props.id);
    }
  }
  async fetchData(id) {
    const user = await fetchUser(id);
    this.setState({ user });
  }
  render() {
    ...
  }
}

Class LifecyclesΒ πŸ˜• change detection

function ProfilePage ({ id }) {
  const [user, setUser] = React.useState(null)
  
  React.useEffect(() => {
    const fetchData = async (id) => {
      const user = await fetchUser(id);
      setUser(user)
    }
  
    fetchData(id)
  }, [id])
  
  ...
}

Side EffectsΒ  πŸ₯³ Synchronization

function DataGrid ({ data, columns }) {
  const rows = React.useMemo(() => {
    // expensive function
    return computeRows({ data, columns })
  }, [data, columns])
  
  ...
}

MemoizationΒ  πŸŽ›Β Computed State

function App() {
  const stuff = useCustomHook()

  return ...
}
  • useState()
  • useReducer()
  • useEffect()
  • useMemo()
  • useCallback()

πŸŽ‰ Custom HooksΒ πŸŽ‰

function App() {
  return (
    <Auth>
      {({ jwt }) => (
        <User jwt={jwt}>
          {({ userId }) => (
            <Filters>
              {({ filter }) => (
                <Todos userId={userId} filter={activeFilter}>
                  {({ todos, isLoading, error }) => (
                    <div>
                      {isLoading
                        ? 'Loading...'
                        : error
                        ? 'Error!'
                        : todos.map(todo => <Todo todo={todo} />)}
                    </div>
                  )}
                </Todos>
              )}
            </Filters>
          )}
        </User>
      )}
    </Auth>
  )
}

HOCs, Render Props...

function App() {
  const jwt = useAuth()
  const { userID } = useUser({ jwt })
  const { activeFilter } = useFilters()
  const { todos, isLoading, error } = useTodos({
    userId,
    filter: activeFilter
  })

  return (
    <div>
      {isLoading
        ? 'Loading...'
        : error
        ? 'Error!'
        : todos.map(todo => <Todo todo={todo} />)}
    </div>
  )
}

πŸŽ‰ Custom HooksΒ πŸŽ‰

  • πŸ‘·β€β™€οΈΒ Build-your-own!
  • πŸ“¦ Portable / Sharable Logic
  • 🧠 Component-Aware Abstractions
  • πŸ” Rapid Iteration

Affordances

πŸ₯‡

Components

==

User Interface

Hooks

==

Business Logic

Migrations

New Projects

  • 🎨 Portable UI Utilities
  • 🌎 Global State
  • 🌐 Server State
  • πŸ’Ό Business Logic

Favorite "use" Cases

???

Dark Mode

function App () {
  const [isDark, setIsDark] = React.useState(false)

  const theme = isDark ? themes.dark : themes.light

  return (
    <ThemeProvider theme={theme}>
      ...
    </ThemeProvider>
  )
}
const matchDark = '(prefers-color-scheme: dark)'

function App() {
  const [isDark, setIsDark] = React.useState(
    () => window.matchMedia(matchDark).matches
  )
  
  ...
}

window.matchMedia

  ...

  React.useEffect(() => {
    const matcher = window.matchMedia(matchDark)
    const onChange = ({ matches }) => setIsDark(matches)
    
    matcher.addListener(onChange)
    
    return () => {
      matcher.removeListener(onChange)
    }
  }, [setIsDark])

  ...

useEffect() + Media Watcher

const matchDark = '(prefers-color-scheme: dark)'

function App() {
  const [isDark, setIsDark] = React.useState(
    () => window.matchMedia && window.matchMedia(matchDark).matches
  )

  React.useEffect(() => {
    const matcher = window.matchMedia(matchDark)
    const onChange = ({ matches }) => setIsDark(matches)
    matcher.addListener(onChange)
    return () => {
      matcher.removeListener(onChange)
    }
  }, [setIsDark])

  const theme = isDark ? themes.dark : themes.light

  return <ThemeProvider theme={theme}>...</ThemeProvider>
}
const matchDark = '(prefers-color-scheme: dark)'

function useDarkMode() {
  const [isDark, setIsDark] = React.useState(
    () => window.matchMedia && window.matchMedia(matchDark).matches
  )

  React.useEffect(() => {
    const matcher = window.matchMedia(matchDark)
    const onChange = ({ matches }) => setIsDark(matches)
    matcher.addListener(onChange)
    return () => {
      matcher.removeListener(onChange)
    }
  }, [setIsDark])

  return isDark
}

useDarkMode()

import useDarkMode from './useDarkMode'

function App() {
  const theme = useDarkMode()
    ? themes.dark
    : themes.light

  return (
    <ThemeProvider theme={theme}>
      ...
    </ThemeProvider>
  )
}

"use"-ing it!

function Menu () {
  return (
    <div>
      ...
    </div>
  )
}

Click Outside

import useClickOutside from './useClickOutside'

function Menu() {
  const menuRef = React.useRef()
  
  const onClickOutside = () => {
    console.log('Clicked Outside!')
  }
  
  useClickOutside(menuRef, onClickOutside)
  
  return (
    <div ref={menuRef}>
      ...
    </div>
  )
}

Too soon? 😏

function useClickOutside(elRef, callback) {
  ...
}
function useClickOutside(elRef, callback) {
  React.useEffect(() => {

    const handleClickOutside = e => {
      if (!elRef?.current?.contains(e.target) && callback)
        callback(e)
      }
    }

    document.addEventListener('click', handleClickOutside, true)
    return () => {
      document.removeEventListener('click', handleClickOutside, true)
    }
  }, [callback, elRef])
}
import useClickOutside from './useClickOutside'

function Menu () {
  const menuRef = React.useRef()
  
  const onClickOutside = () => {
    console.log('Clicked Outside!')
  }
  
  useClickOutside(menuRef, onClickOutside)
  
  return (
    <div ref={menuRef}>
      ...
    </div>
  )
}

Something is 🐠-y

eslint-plugin-react-hooks

React.useEffect(() => {
  ...
}, [])
React.useEffect(() => {
  ...
}, [foo, bar])
import useClickOutside from './useClickOutside'

function Menu () {
  const menuRef = React.useRef()
  
  const onClickOutside = () => {
    console.log('Clicked Outside!')
  }
  
  useClickOutside(menuRef, onClickOutside)
  
  return (
    <div ref={menuRef}>
      ...
    </div>
  )
}

React.useCallback

function useClickOutside(elRef, callback) {
  React.useEffect(() => {

    const handleClickOutside = e => {
      if (elRef?.current?.contains(e.target) && callback)
        callback(e)
      }
    }

    document.addEventListener('click', handleClickOutside, true)
    return () => {
      document.removeEventListener('click', handleClickOutside, true)
    }
  }, [callback, elRef])
}
import useClickOutside from './useClickOutside'

function Menu () {
  const menuRef = React.useRef()
  
  const onClickOutside = React.useCallback(() => {
    console.log('Clicked Outside!')
  }, [])
  
  useClickOutside(menuRef, onClickOutside)
  
  return (
    <div ref={menuRef}>
      ...
    </div>
  )
}
function useClickOutside(elRef, callback) {
  React.useEffect(() => {

    const handleClickOutside = e => {
      if (elRef?.current?.contains(e.target) && callback)
        callback(e)
      }
    }

    document.addEventListener('click', handleClickOutside, true)
    return () => {
      document.removeEventListener('click', handleClickOutside, true)
    }
  }, [callback, elRef])
}
function useClickOutside(elRef, callback) {
  
  
  
  React.useEffect(() => {

    const handleClickOutside = e => {
      if (elRef?.current?.contains(e.target) && callback)
        callback(e)
      }
    }

    document.addEventListener('click', handleClickOutside, true)
    return () => {
      document.removeEventListener('click', handleClickOutside, true)
    }
  }, [callback, elRef])
}
function useClickOutside(elRef, callback) {
  const callbackRef = React.useRef()
  callbackRef.current = callback
  
  React.useEffect(() => {

    const handleClickOutside = e => {
      if (elRef?.current?.contains(e.target) && callbackRef.current)
        callbackRef.current(e)
      }
    }

    document.addEventListener('click', handleClickOutside, true)
    return () => {
      document.removeEventListener('click', handleClickOutside, true)
    }
  }, [callbackRef, elRef])
}
import useClickOutside from './useClickOutside'

function Menu () {
  const menuRef = React.useRef()
  
  const onClickOutside = () => {
    console.log('Clicked Outside!')
  }
  
  useClickOutside(menuRef, onClickOutside)
  
  return (
    <div ref={menuRef}>
      ...
    </div>
  )
}

Look Ma! No useCallback!

function useClickOutside(elRef, callback) {
  const callbackRef = React.useRef()
  callbackRef.current = callback
  
  React.useEffect(() => {

    const handleClickOutside = e => {
      if (elRef?.current?.contains(e.target) && callbackRef.current)
        callbackRef.current(e)
      }
    }

    document.addEventListener('click', handleClickOutside, true)
    return () => {
      document.removeEventListener('click', handleClickOutside, true)
    }
  }, [callbackRef, elRef])
}

πŸŽ‰ useClickOutside.js πŸŽ‰

State

Global

Global State

You

function Todos () {
  const { todos } = useStore()
}

😌

import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'

const selectNumOfDoneTodos = createSelector(
  state => state.todos,
  todos => todos.filter(todo => todo.isDone).length
)

function Todos () {
  const NumOfDoneTodos = useSelector(selectNumOfDoneTodos)
  return <div>{NumOfDoneTodos}</div>
}

😳 WAT 😳

"Finding Global State"

const context = React.createContext()

export const StoreProvider = ({ children, initialState = {} }) => {
  const [store, setStore] = React.useState(() => initialState)

  const contextValue = React.useMemo(() => [store, setStore], [store])

  return (
    <context.Provider value={contextValue}>
      {children}
    </context.Provider>
  )
}

export default function useStore() {
  return React.useContext(context)
}

Creating a Global Store

import { StoreProvider } from './useStore'

const initialState = {
  todos: []
}

function App () {
  return (
    <StoreProvider initialState={initialState}>
      <Todos />
    </StoreProvider>
  )
}

<App>

import useStore from './useStore'

function Todos () {
  const [{ todos }, setStore] = useStore()

  const addTodo () => {
    setStore(old => ({
      ...old,
      todos: [...old.todos, { name: 'New Todo' }]
    })
  }
  ...
}

Consuming the store

πŸ€”Β setState?

😎 useReducer

import { StoreProvider } from './useStore'

const initialState = {
  todos: []
}

const reducer = (state, action) => {
  switch (action.type) {
    case 'addTodo':
      return {
        ...state,
        todos: [
          ...state.todos,
          action.todo
        ]
      }
    default:
      throw new Error('Unknown action!', action);
  }
}

function App () {
  return (
    <StoreProvider reducer={reducer} initialState={initialState}>
      <Todos />
    </StoreProvider
  )
}

<App>

export function StoreProvider ({
  children,
  reducer,
  initialState = {}
}) {
  const [store, dispatch] = React.useReducer(
    reducer,
    initialState
  )

  const contextValue = React.useMemo(
    () => [store, dispatch],
    [store, dispatch]
  )

  return (
    <context.Provider value={contextValue}>
      {children}
    </context.Provider>
  )
}

Global Store

import useStore from './useStore'

function Todos () {
  const [{ todos }, dispatch] = useStore()
  
  const addTodo () => dispatch({
    type: 'addTodo',
    todo: {
      name: 'New Todo'
    }
  })

  return (
    <div>
      {todos.map(todo => (
        <Todo key={todo.id} todo={todo} />
      ))}
    </div>
  )
}

<Todos>

import useStore from './useStore'

function Todo ({ todo }) {
  const [, dispatch] = useStore()

  const handleClick = () => {
    dispatch({
      type: 'toggleTodo',
      todoId: todo.id
    })
  }

  return (
    <div onClick={handleClick}>{todo.name}</div>
  )
}

<Todo>

πŸ€”Β unnecessary renders?

😎 multiple contexts

😎 multiple contexts

Multi-Context Global Store

const storeContext = React.createContext()
const dispatchContext = React.createContext()

export const StoreProvider = ({ children, reducer, initialState = {} }) => {
  const [store, dispatch] = React.useReducer(reducer, initialState)

  return (
    <dispatchContext.Provider value={dispatch}>
      <storeContext.Provider value={store}>
        {children}
      </storeContext.Provider>
    </dispatchContext.Provider>
  )
}

export function useStore() {
  return React.useContext(storeContext)
}

export function useDispatch() {
  return React.useContext(dispatchContext)
}

Kent C. Dodds

import { useStore } from './useStore'
import Todo from './Todo'

function Todos () {
  const { todos } = useStore()

  return (
    <div>
      {todos.map(todo => (
        <Todo key={todo.id} todo={todo} />
      ))}
    </div>
  )
}

<Todos>

import { useDispatch } from './useStore'

function Todo ({ todo }) {
  const dispatch = useDispatch()

  const handleClick = () => {
    dispatch({ type: 'toggleTodo', todoId: todo.id })
  }

  return (
    <div onClick={handleClick}>{todo.name}</div>	
  )
}

<Todo>

πŸ€”Β single store?

😎 multiple stores

Multiple Stores w/ makeStore()

export default function makeStore (reducer, initialState) {
  const dispatchContext = React.createContext()
  const storeContext = React.createContext()
  
  const StoreProvider = ({ children }) => {
    const [store, dispatch] = React.useReducer(reducer, initialState)
  
    return (
      <dispatchContext.Provider value={dispatch}>
        <storeContext.Provider value={store}>
          {children}
        </storeContext.Provider>
      </dispatchContext.Provider>
    )
  }
  
  function useDispatch() {
    return React.useContext(dispatchContext)
  }
  
  function useStore() {
    return React.useContext(storeContext)
  }

  return [StoreProvider, useDispatch, useStore]
}

Todos Store

import makeStore from './makeStore'

const todosReducer = (state, action) => {...}

const [
  TodosProvider,
  useTodos,
  useTodosDispatch
] = makeStore(todosReducer, [])

export { TodosProvider, useTodos, useTodosDispatch }
import { TodosProvider } from './useTodosStore'

function App () {
  return (
    <TodosProvider>
        <Todos />
    </TodosProvider
  )
}

<App>

import { TodosProvider } from './useTodosStore'
import { LayoutProvider } from './useLayoutStore'

function App () {
  return (
    <TodosProvider>
      <LayoutProvider>
        <Todos />
     </LayoutProvider>
    </TodosProvider
  )
}

<App>

<Menu>

import useClickOutside from './useClickOutside'

function Menu () {
  const menuRef = React.useRef()
  
  const onClickOutside = () => {
    console.log('Clicked Outside!')
  }
  
  useClickOutside(menuRef, onClickOutside)
  
  return (
    <div ref={menuRef}>
      ...
    </div>
  )
}
import useClickOutside from './useClickOutside'
import { useLayoutStore, useDispatch } from './useLayoutStore'

function App() {
  const { menuIsOpen } = useStore()
  const dispatch = useDispatch()
  const elRef = React.useRef()

  useClickOutside(elRef, () => 
    dispatch({ type: 'menuToggled', isOpen: false })
  )

  return (
    <div ref={elRef} onClick={handleOpen}>
      ...
    </div>
  )
}

<Menu>

πŸ€” No persistance?

😎 Local Storage

Global Store + localStorage

export default function makeStore(userReducer, initialState, key) {
  const dispatchContext = React.createContext()
  const storeContext = React.createContext()

  try {
    initialState = JSON.parse(localStorage.getItem(key)) || initialState
  } catch {}

  const reducer = (state, action) => {
    const newState = userReducer(state, action)
    localStorage.setItem(key, JSON.stringify(newState))
    return newState
  }

  const StoreProvider = ({ children }) => {
    const [store, dispatch] = React.useReducer(reducer, initialState)

    return (
      <dispatchContext.Provider value={dispatch}>
        <storeContext.Provider value={store}>{children}</storeContext.Provider>
      </dispatchContext.Provider>
    )
  }

  function useDispatch() {
    return React.useContext(dispatchContext)
  }

  function useStore() {
    return React.useContext(storeContext)
  }

  return [StoreProvider, useDispatch, useStore]
}

😌

😳

πŸ™Β Remote Data Persistance?

😎 Server State

πŸ€”

syncΒ  Β  Β Β async

dispatchΒ  Β  Β  mutation

storeΒ  Β  Β  server

import { useDispatch } from './useTodosStore'

function Todo ({ todo }) {
  const dispatch = useDispatch()

  const handleClick = () => {
    dispatch({ type: 'toggleTodo', todoId: todo.id })
  }

  return (
    <div onClick={handleClick}>{todo.name}</div>
  )
}
import { useTodos } from './useTodosStore'

function Todos () {
  const todos = useTodos()

  return (
    <div>
      {todos.map(todo => (
        <Todo key={todo.id} todo={todo} />
      ))}
    </div>
  )
}
import { ??? } from './???'

function Todo ({ todo }) {
  const toggleTodo = ???()

  const handleClick = () => {
    toggleTodo(todo.id)
  }

  return (
    <div onClick={handleClick}>{todo.name}</div>
  )
}
import { ??? } from './???'

function Todos () {
  const todos = ???()

  return (
    <div>
      {todos.map(todo => (
        <Todo key={todo.id} todo={todo} />
      ))}
    </div>
  )
}

Business Logic

User Interface

  • Shared State
  • Utilities
  • Computation
  • Etc...
  • Local State
  • Markup
  • UI Events
  • Styles

Components

Component

Component

Store

useBusinessLogic()

<Component />

<Todos />

Store / Server

useTodos()

Custom Hooks are πŸ†“!!!

import useTodos from './useTodos'

function Todos () {
  const todos = useTodos()

  return (
    <div>
      {todos.map(todo => (
        <Todo key={todo.id} todo={todo} />
      ))}
    </div>
  )
}

Business Logic Hooks

useTodos.js

export function useTodos() {
  return whateverWeWant()
}
import { useTodosStore } from './useTodosStore'

export function useTodos() {
  const todos = useTodosStore()

  return todos
}

Using the in-memory store

import { useTodosStore } from './useTodosStore'

export function useTodos() {
  const todos = useTodosStore()

  return {
    todos,
    isLoading: false,
    error: null
  }
}

Assume async data

export function useTodos() {
  const [todos, setTodos] = React.useState([])
  const [isLoading, setIsLoading] = React.useState(false)
  const [error, setError] = React.useState(null)
  
  const fetchTodos = React.useCallback(async () => {
    setIsLoading(true)
    
    try { 
      const { data: todos } = await axios.get('/todos')
      setTodos(todos)
    } catch (err) {
      setError(err)
    }

    setIsLoading(false)
  }, [setIsLoading, setTodos, setError])
  
  React.useEffect(() => {
    fetchTodos()
  }, [fetchTodos])

  return {
    todos,
    isLoading,
    error
  }
}

useEffect() + axios

import usePromise from './usePromise'

export function useTodos() {
  const getTodos = React.useCallback(async () => {
    const { data } = await axios.get('/todos')
    return data
  }, [])

  const { data: todos, isLoading, error } = usePromise(getTodos)

  return {
    todos,
    isLoading,
    error,
  }
}

usePromise() + REST

Component

Component

REST GET

useTodos()

<Todos />

<Todo />

REST POST

useToggleTodo()

<Todo>

import useToggleTodo from './useToggleTodo'

function Todo ({ todo }) {
  const toggleTodo = useToggleTodo()

  const handleClick = () => {
    toggleTodo(todo.id)
  }

  return (
    <div onClick={handleClick}>{todo.name}</div>
  )
}

<Todo>

import useToggleTodo from './useToggleTodo'

function Todo ({ todo }) {
  const [toggleTodo, { isLoading, error }] = useToggleTodo()

  const handleClick = () => {
    toggleTodo(todo.id)
  }

  return (
    <div onClick={handleClick}>{todo.name}</div>
  )
}

useToggleTodo()

export function useToggleTodo() {
  ...

  return [toggleTodo, { isLoading, error }]
}

Using the in-memory store

import { useDispatch } from './todosStore'

export function useToggleTodo() {
  const dispatch = useDispatch()

  const toggleTodo = React.useCallback(id => {
    dispatch({
      type: 'toggle_todo',
      todoId: id,
    })
  })

  return [toggleTodo, { isLoading: false, error: null }]
}
export function useToggleTodo() {
  const [isLoading, setIsLoading] = React.useState(false)
  const [error, setError] = React.useState(null)

  const toggleTodo = React.useCallback(
    async todoId => {
      setIsLoading(true)

      try {
        const { data } = await axios.get(
          `https://example.com/todos/${todoId}/toggle`
        )
        return data
      } catch (err) {
        setError(err)
      }

      setIsLoading(false)
    },
    [setIsLoading, setError]
  )

  return [toggleTodo, { isLoading, error }]
}

Using REST

πŸ€”

  • Caching?
  • Multiple useTodos()?
  • Race Conditions?
  • Stale Requests?
  • When do we refetch?

<Todos />

Store / Server

useTodos()

React Query

import { useQuery } from 'react-query'

const fetchTodos = () =>
  axios.get('https://example.com/todos').then(res => res.data)

export function useTodos() {
  const { data: todos, isLoading, error } = useQuery('todos', fetchTodos)
  
  return { todos, isLoading, error }
}

useTodos() + React Query

πŸ€”

  • Server Side-Effects?
  • Stale useTodos()?

<Todos />

Store / Server

useToggleTodo()

React Query

<Todo>

import useToggleTodo from './useToggleTodo'

function Todo({ todo }) {
  const [toggleTodo, { isLoading, error }] = useToggleTodo()

  const handleClick = () => toggleTodo(todo.id)

  return isLoading ? (
    'Toggling...'
  ) : error ? (
    'Error!'
  ) : (
    <div onClick={handleClick}>{todo.name}</div>
  )
}
import { useMutation } from 'react-query'

const getToggleTodoById = todoId =>
  axios.get(`https://example.com/todos/${todoId}/toggle`)
    .then(res => res.data)

export function useToggleTodo() {
  return useMutation(getToggleTodoById, {
    refetchQueries: ['todos']
  })
}

useToggleTodo() + React Query

import { useQuery } from '@apollo/react-hooks';
import gql from 'graphql-tag';

const GET_TODOS = gql`
  query getTodos {
    ...
  }
`;

export function useTodos() {
  const {
    data: todos,
    loading: isLoading,
    error
  } = useQuery(GET_TODOS);
  
  return { todos, isLoading, error }
}

useTodos() + Apollo

import { useMutation } from '@apollo/react-hooks'
import gql from 'graphql-tag'

const TOGGLE_TODO = gql`
  mutation ToggleTodo($todoId: Id!) {
    toggleTodo(id: $todoId) {
      id
      done
    }
  }
`

export function useToggleTodo() {
  const [toggleTodo, { loading: isLoading, error }] = useMutation(TOGGLE_TODO, {
    refetchQueries: ['getTodos']
  })
  return [toggleTodo, { isLoading, error }]
}

useToggleTodo() + Apollo

😲

  • In-Memory
  • Promises
  • React Query
  • Apollo

😎

import useTodos from './useTodos'
import useToggleTodo from './useToggleTodo'

                    ...
 
                 useTodos()
              useToggleTodos()

Component

Component

Component

Custom Hook

Custom Hook

Store

API

Custom Hook

Component

Component

Component

UI

Business & UI Logic

Data

Services

Integrations

Utilities

usePermissions

useDebounce

useToast

usePagination

useLoading

useSubscription

useAuth

useModal

useClipboard

useForm

useTooltip

useUser

github.com

youtube.com

twitter.com

/ tannerlinsley

Custom Hooks in React: The ultimate UI abstraction layer you're missing out on

By Tanner Linsley

Custom Hooks in React: The ultimate UI abstraction layer you're missing out on

The slides for my talk at JS Conf Hawaii 2020

  • 3,194