SEATTLE / SÃO PAULO / PANAMÁ
BOGOTÁ / MEDELLÍN
- Frontend Developer at S4N 👷
- Open Source Contributor 👨💻
- Developer who loves UX 💫
- Game developer (Hobby) 🎮
- Chocolate lover 🍫
- Creator of Proyecto26 🧚
Be the best version of yourself! 💪
HOOKS IN REACT
LIFECYCLE
STATE MANAGEMENT
EXTEND BEHAVIOR
REAL-TIME CONNECTIONS
Component State
Hooks & Actions
Testing Hooks
Real-time
function CustomListContainer() {
  const [error, setError] = React.useState();
  const [items, setItems] = React.useState([]);
  
  useEffect(() => {
    getItems()
      .then(setItems)
      .catch(setError)
  }, [])
  
  return error ? <ErrorContainer error={error} /> : (
    <IonList>
      items.map(item => (
      	<IonItem>
          <IonLabel>{ item.name }</IonLabel>
        </IonItem>
      ))
    </IonList>
  )
}composable stateful interfaces 💡
- Reusable components
- Composition over inheritance
- Follow the kiss principle
- It's possible to simulate behaviors of class components with hooks
// Try to avoid this pattern if possible.
const [ignored, forceUpdate] = useReducer(x => x + 1, 0)- Do Hooks cover all use cases for classes? No
function SignInScreen() {
  // useState is not the right way for complex state logic 
  const [userName, setUserName] = React.useState(getName());
  const [password, setPassword] = React.useState('');
  const [error, setError] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  
  // Using previous state
  const onLogin = async () => {
    try {
      setLoading(true)
      await authenticate(userName, password)
      ...
    } catch (error) {
      setError(error)
    } finally {
      setLoading(false)
    }
  }
  
  // We can't test this code directly
  const onFunA = () => ...
  
  // Creating a new function on every render
  const onFunB = () => ...
   
  return ...
}function SignInScreen () {
  const [state, dispatch] = useReducer<SignInReducer>(reducer, {
    error: null,
    loading: false,
    userName: '',
    ...
  })
}import { Reducer } from 'react'
type OptionError = Error | null
interface SignInScreenState {
  error: OptionError,
  loading?: boolean,
  userName?: string,
}
export enum Action {
  SetError,
  SetLoading,
  SetUsername
}
type SignInScreenAction =
 | { type: Action.SetError, value: OptionError }
 | { type: Action.SetLoading, value: boolean }
 | { type: Action.SetUsername, value: string }
type SignInReducer =
  Reducer<SignInScreenState, SignInScreenAction>const reducer = (
  state: SignInScreenState,
  action: SignInScreenAction
): SignInScreenState => {
  switch (action.type) {
    case ACTIONS.SET_ERROR:
      return {
        ...state,
        error: action.value
      }
    case ACTIONS.SET_LOADING:
      return {
        ...state,
        loading: action.value
      }
    case ACTIONS.SET_USERNAME:
      return {
        ...state,
        userName: action.value
      }
    ...
    default:
      return state
  }
}more control for
performance
A way to reuse stateful logic 💡
function useAuth() {
  // Use stores with React Context API, etc
  const [state, dispatch] = useStore();
  // memoized function for performance
  const onSignIn = useCallback(async (
    document: string,
    password: string,
  ) => {
    const { data: { access_token: accessToken } } = await axios.post(
      authEndpoint,
      { document, password }
    );
    dispatch({ type: Action.LoadToken, value: accessToken });
  }, [dispatch]);
  
  return {
    state,
    onSignIn
  };
}function useAuth() {
  ...
  const onLoadUser = useCallback(async () => {
    const { data: user } = await axios.get(`${userEndpoint}/me`, {
      headers: { 'Authorization': `Bearer ${state.token}` }
    });
    dispatch({
      type: Action.LoadUser,
      value: user
    });
  }, [state.token, dispatch]);
  
  return {
    ...,
    onLoadUser
  }
}Only changes if one of the dependencies has changed 💡
- Single source of truth
- No code duplication
- All actions are visible
- No more need the deep component tree nesting reusing state logic (HOCs can be used for specific cases like adding behaviors, injecting stores via props, etc)
- Many components can react to same action
- Test components and hooks separately
import { act, renderHook } from '@testing-library/react-hooks'
import { useDictionary } from '..'
describe('useDictionary hook', () => {
  it('should handle onUpdateValue action', () => {
    const initialState = {
      boolean: false,
    }
    const { result } = renderHook(() => useDictionary(initialState))
    act(() => {
      result.current.onUpdateValue('boolean', true)
    })
    expect(result.current.state.boolean).toBeTruthy()
  })
})Encourage good testing practices 💡
it('should handle useEffect hook', async () => {
  const spyInitialize = jest.spyOn(HubConnection, 'initialize').mockResolvedValueOnce()
  const spyClose = jest.spyOn(HubConnection, 'close').mockReturnValueOnce()
  const { result, waitForNextUpdate, unmount } = renderHook(() => useMyHook())
  await waitForNextUpdate()
  expect(spyInitialize).toHaveBeenCalled()
  expect(result.current.initialized).toBeTruthy()
  unmount()
  expect(spyClose).toHaveBeenCalled()
})Just use the hook directly and assert the results 💡
import { useState, useEffect, useCallback } from 'react'
import io, { Socket } from 'socket.io-client'
import { WS_DOMAIN } from '../../contants'
export function useSocket () {
  const [socket, setSocket] = useState<typeof Socket | null>(null)
  useEffect(function () {
    const newSocket = io.connect(WS_DOMAIN)
    setSocket(newSocket)
    return () => {
      newSocket.removeAllListeners()
      newSocket.close()
    }
  }, [])
  const onSendMessage = useCallback((eventName: string, data: any) => {
    if(socket) socket.emit(eventName, data)
  }, [socket])
  return {
    socket,
    onSendMessage
  }
}import { useState, useEffect, useCallback } from 'react'
import io, { Socket } from 'socket.io-client'
import { WS_DOMAIN } from '../../contants'
import { useAuth } from '../useAuth'
export function useSocketWithAuth () {
  const { state } = useAuth()
  const [socket, setSocket] = useState<typeof Socket | null>(null)
  useEffect(function () {
    if (!state.token) return
    const newSocket = io.connect(WS_DOMAIN, {
      query: { token: state.token }
    })
    setSocket(newSocket)
    return () => {
      newSocket.removeAllListeners()
      newSocket.close()
    }
  }, [state.token])
  
  return {
    socket
  }
}DEMO / REACT / HOOKS
REAL-TIME / GAMES
Of course Elon, Code here! 🎁
- Additional Hooks: specific edge cases
- React Hooks: Why are they neccesary?
- Adios Redux: using React hooks and Context effectively
- Functional-Light JavaScript: Pragmatic, balanced FP in JavaScript
- Composing Software: The Book
Assets of this presentation are part of S4N Styleguide
Juan D. Nicholls
juannicholls@s4n.co