In this post, I will:
- Give a quick introduction to hooks
- Share some pitfalls, lessons learned working with hooks
- Share awesome resources for diving deeper
If you want to play with the code samples then open https://codesandbox.io/s/new and paste them in as you go.
What are hooks?
A hook is a function provided by React that lets you hook into React features from your function components - Dan Abramov
React hooks make components simpler, smaller, and more re-usable without using mixins.
React Hooks were released with React 16.8, February 2019 which in tech makes them quite old at this point 😊 Hooks have made a radical shift in how components are being developed. Before, the recommendation was to use Class components and Pure functional components, components without state only props.
This meant you may have started writing a Pure component, only to find out you needed state or lifecycle methods, so you had to refactor it into a class.
Introduce hooks. Hooks allow functional components to use all of React's features. But what is even more powerful is it allows components to separate visual render logic and "business" logic.
Your first hook - useState
useState allows a functional component to... well... use state 😄
Let's see an example:
function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
)
}
But how can a function keep track of state?
If you're sharp, then you may ask yourself straightaway "How can a function keep track of state?". How does a simple variable in a function replace a class field?
Remember, when using classes React only has to call the render()
function but with a function component it calls the entire function again, so how is state kept between renders?
Here's a class component as a refresher:
class Counter extends Component {
constructor() {
this.state = { count: 0 }
}
render() {
return (
<button
onClick={this.setState({
count: count + 1,
})}
>
{count}
</button>
)
}
}
Hooks have to be run in the same order every time, this means no hooks inside of if statements! Conceptually, you can think of hooks as being stored in an array where every hook has its own index as a key. So the value of our count
variable above would be hookArray[countHookIndex]
.
Without help, this would be an easy mistake to make which is why React has published a couple of ESLint rules to help us.
Let's dive into where most mistakes happen, the useEffect
hook.
Side effects with hooks - useEffect
What do I mean by side effects? Things such as:
- Fetching data on mount
- Setting up event listeners
- Cleaning up listeners on dismount
Here's an example of setting up an event listener "on mount":
useEffect(() => {
const handleKeyUp = e => {
if (e.key === 'j') {
alert('You pressed j')
}
}
document.addEventListener(
'keyup',
handleKeyUp
)
return () => {
document.removeEventListener(
'keyup',
handleKeyUp
)
}
}, [])
Why is unmount in quotes? Because there are no hooks matching the lifecycle methods such as componentDidMount()
there's an entirely new way of thinking with useEffect
.
The second parameter of useEffect
is what is called a dependency array. Since I've added an empty array, the code is run once (on mount), because the dependencies never change.
If I omitted the argument, the code would run on every render and update.
The React team noticed that setting up and removing listeners is part of the same abstraction and thus the code should be co-located therefore when an effect returns a function it will be run in the cleanup phase, that is, between renders.
While confusing at first, this is extremely powerful. You can add state variables to the dependency array! Essentially allowing you to "watch" state variables.
Dependency array pitfalls
Luckily, most of the pitfalls can be caught by using the ESLint rules from earlier. But it is good to understand why, such that, when you encounter a problem the plugin did not account for, you can solve it yourself.
I should also mention there are a few other hooks that also use dependency arrays: useMemo
and useCallback
but I won't cover these here.
What I failed to think about for a long time was that you're passing a function to useEffect
and that function has a closure over your functional component's state and other variables. I will explain closures in a bit, but let's first see an example of how this goes wrong:
function Counter() {
const [count, setCount] = useState(0)
useEffect(() => {
const timer = setInterval(
() => console.log('count', count),
1000
)
return () => clearInterval(timer)
}, []) // ESLint warns us we're missing count
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
)
}
Now, this is a really contrived example but the above example will log count 0
every second regardless of how many times the user presses the button. This is because arrow function passed to useEffect
is created on mount and never again thus count
will remain 0
because it is a closure over the first "instance" of the function. We have a stale closure.
What is a closure?
The simplest definition I've heard is a closure allows a function to keep private variables.
Let's see an example:
function createCounter() {
let count = 0
return () => {
count += 1
return count
}
}
const counter = createCounter()
counter() // 1
counter() // 2
Now if you want to create just one counter you can wrap it in a module, an IIFE - immediately invoked function expression:
const counter = (() => {
let count = 0
return () => {
count += 1
return count
}
})()
counter() // 1
counter() // 2
That's a lot of functions 🤯 Let's break it down:
-
createCounter()
was a function that returned a function. - So we let
createCounter
call itself straight away, creating the closure for thecount
. That hidescount
from the outer scope.
If you were a developer during the jQuery days this will seem very familiar to you. IIFE's were the go-to way of creating modules, avoiding having everything in the global scope, since CommonJS (require and module.exports) and ECMAScript Modules, ESM (import/export) were not created yet.
Dive deeper
- Swyx creating a simple React from scratch
- Kent Dodds accordion video
- Tanner Linsley custom hooks video
- Kent Dodds on hook pitfalls
- useHooks library
I hope you enjoyed this quick introduction to hooks. If you have any questions, feel free to comment below!
Top comments (0)