J.D Nicholls
Founding Full-Stack Web3 Engineer at @baxusco | Digital nomad 🎒 | Mentor 👨🏫 | Speaker 🗣️ | Developer 👨💻 | Creator of @proyecto26 #opensource #developer
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
By J.D Nicholls
Component State, Hooks & Actions, Testing Hooks and Real-time
Founding Full-Stack Web3 Engineer at @baxusco | Digital nomad 🎒 | Mentor 👨🏫 | Speaker 🗣️ | Developer 👨💻 | Creator of @proyecto26 #opensource #developer