EpicReact - React Hooks
useState
const [count, setCount] = React.useState(0)
- It accepts one argument
- It returns two values:
- state value
- function to update the state value (convention is to prefix by:
set
)
Example of a useState
in a form:
function Greeting({initialName = ''}) {
const [name, setName] = React.useState(initialName)
function handleChange(event) {
setName(event.target.value)
}
return (
<div>
<form>
<label htmlFor="name">Name: </label>
<input onChange={handleChange} id="name" />
</form>
{name ? <strong>Hello {name}</strong> : 'Please type your name'}
</div>
)
}
function App() {
return <Greeting initialName="Carlos" />
}
Lazy loader
Load the default value the first time this component is rendered. We use a function in the default value.
Eg:
const [name, setName] = React.useState(
() => window.localStorage.getItem('name') || initialName,
)
useEffect
React.useEffect(() => {
// your side-effect code here.
// this is where you can make HTTP requests or interact with browser APIs.
})
Eg.: Set the value of a localStorage item
React.useEffect(() => {
window.localStorage.setItem('name', name)
})
Effect dependency
Instead of executing the useEffect
every time, we can link it’s execution to a variable value onChange. E.g: to the name
variable (normally is a useState
variable)
React.useEffect(() => {
window.localStorage.setItem('name', name)
}, [name])
HTTP requests
Doing the HTTP requests inside the useEffect
hook allows us to ensure that whenever certain changes take place, we apply the side-effects based on those changes.
React.useEffect(() => {
async function effect() {
const result = await doSomeAsyncThing()
// do something with the result
}
effect()
})
Maybe it’s easier to extract all the async code into a utility function which I call and then use the promise-based .then method instead of using async/await syntax:
React.useEffect(() => {
doSomeAsyncThing().then(result => {
// do something with the result
})
})
Considerations
- handle errors
fetchPokemon(pokemonName).then( pokemon => setPokemon(pokemon), error => setError(error), )
- use status better that too many if/else. Eg:
- idle: no request made yet
- pending: request started
- resolved: request successful
- rejected: request failed
An easy way to manage the status
and the values, we could storage it together in an Object:
const [state, setState] = React.useState({
status: 'idle',
pokemon: null,
error: null,
})
const {status, pokemon, error} = state
// and to update each property
setState({status: 'resolved', pokemon})
In future, this could be done with the useReducer
hook
Custom Hooks
We can extract the useState
and useEffect
hooks, into a new function Hook.
E.g: A hook that get and set the value of a local storage item
function useLocalStorageState(key, defaultValue = '') {
const [state, setState] = React.useState(
() => window.localStorage.getItem(key) || defaultValue,
)
React.useEffect(() => {
window.localStorage.setItem(key, state)
}, [key, state])
return [state, setState]
}
in our app, we can use this custom hook:
const [name, setName] = useLocalStorageState('name', initialName)
function handleChange(event) {
setName(event.target.value)
}
Lifting state
How to share state between two sibling components? The idea is to find the first common parent and placing the state management there.
In App
component, we place the state management for the three components FavoriteAnimal
, Name
and Display
.
Note how we use the onChange
to pass the function that change the value of the state management.
function Name({name, onNameChange}) {
return (
<div>
<label htmlFor="name">Name: </label>
<input id="name" value={name} onChange={onNameChange} />
</div>
)
}
function FavoriteAnimal({animal, onAnimalChange}) {
return (
<div>
<label htmlFor="animal">Favorite Animal: </label>
<input id="animal" value={animal} onChange={onAnimalChange} />
</div>
)
}
function Display({name, animal}) {
return <div>{`Hey ${name}, your favorite animal is: ${animal}!`}</div>
}
function App() {
const [animal, setAnimal] = React.useState('')
const [name, setName] = React.useState('')
return (
<form>
<Name name={name} onNameChange={event => setName(event.target.value)} />
<FavoriteAnimal
animal={animal}
onAnimalChange={event => setAnimal(event.target.value)}
/>
<Display name={name} animal={animal} />
</form>
)
}
useRef
After a component has been rendered, it’s considered “mounted.” That’s when the React.useEffect callback is called. So often you’ll do direct DOM interactions/manipulations in the useEffect
callback.
- we need to create the
const tiltRef = React.useRef()
- in the elm, we need to add the
ref={tiltRef}
to associate it - in
useEffect
hook, use theuseRef
to the element created
function Tilt() {
const tiltRef = React.useRef()
React.useEffect(() => {
const tiltNode = tiltRef.current // const {current: tiltNode} = tiltRef
// add a new class to the useRef element
tiltNode.classList.add('carlos')
}, [])
return (
<div ref={tiltRef} className="tilt-root">
Ola k ase
</div>
)
}
Error Boundaries
It’s a simple way to handle errors. Check the react-error-boundary package.