Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris
Hooks are an upcoming feature that lets you use state and other React features without writing a class component - functions FTW.
Hooks is the latest pattern and an experimental feature that's supposedly better than sliced bread. Everyone used to go nuts over Render props but now it's all hooks.
Hooks are currently in React v16.8.0-alpha.0 so you can try them out already 😃
![](/assets/Screen Shot 2019-01-22 at 15.18.11.png)
Every time something new comes out we get excited. It's ketchup, it's the best thing since sliced bread and so on. We hope that this will finally be the solution to all our problems, so we use it, again and again and again. We've all been guilty of doing this at one time of another, abusing a pattern or paradigm and yes there has always been some truth to it that the used pattern has been limited.
Below I will try to lay out all the different pain points that makes us see Hooks as this new great thing. A word of caution though, even Hooks will have drawbacks, so use it where it makes sense. But now back to some bashing and raving how the way we used to build React apps were horrible;)
There are many problems Hooks are trying to address and solve. Here is a list of offenders:
providers
, consumers
, higher-order components
, render props
, and other abstractions, exhausted yet? 😉Like the whole wrapping itself wasn't bad enough we need to restructure our components which is tedious, but most of all we loose track over how the data flows.
componentDidMount
and componentDidUpdate
. Same componentDidMount
method might also contain some unrelated logic that sets up event listeners, with cleanup performed in componentWillUnmount
Just create smaller components?
In many cases it’s not possible because:
this
works in JavaScript, you have to bind them to event handlers etc.
The distinction between function and class components in React and when to use each one leads to disagreements and well all know how we can be when we fight for our opinion, spaces vs tabs anyone 😃?.Hooks let you use more of React’s features without classes. Not only that, we are able to create Hooks that will allow you to:
Hooks let you split one component into smaller functions
based on what pieces are related
(such as setting up a subscription or fetching data), rather than forcing a split based on lifecycle methods.
Let's have an overview of the different Hooks available to use. Hooks are divided into Basic Hooks
and Additional Hooks
. Let's list the Basic Hooks
first and mention briefly what their role is:
We will focus on useState
and useEffect
in this article.
We will not be covering Additional Hooks
at all as this article would be way too long but you are encouraged to read more about them on Additional Hooks
useState
, it accepts a reducer and returns a pair with the current state and a dispatch
functionuseMemo
will only recompute the memoized value when one of the inputs has changed. This optimization helps to avoid expensive calculations on every render..current
property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the componentAs you can see above I've pretty much borrowed the explanation for each of these Additional Hooks
from the documentation. The aim was merely to describe what exist, give a one liner on each of them and urge you to explore the documentation once you feel you've mastered the Basic Hooks
.
This hook let's us use state inside of a function component. Yep I got your attention now right? Usually that's not possible and we need to use a class
for that. Not anymore. Let's show what using useState
hook looks like. We need to do two things to get started with hooks:
react
and react-dom
The first one we will solve by typing:
npx create-react-app hooks-demo
next we need to upgrade react
and react-dom
so they are using the experimental version of React where hooks are included:
yarn add react@next react-dom@next
Now we are good to go:
import React, { useState } from 'react';
const Counter = () => {
const [counter, setCounter] = useState(0);
return (
<div>
{counter}
<button onClick={() => setCounter(counter + 1)} >Increment</button>
</div>
)
}
export default Counter;
Ok we see that we use the Hook useState
by invoking it and we invoke it like so:
useState(0)
This means we give it an initial value of 0. What happens next is that we get an array back that we do a destructuring on. Let's examine that closer:
const [counter, setCounter] = useState(0);
Ok, we name the first value in the array counter
and the second value setCounter
. The first value is the actual value that we can showcase in our render
method. The second value setCounter()
is a function that we can invoke and thereby change the value of counter
. So in a sense, setCounter(3)
is equivalent to writing:
this.setState({ counter: 3 })
Just to ensure we understand how to use it fully let's create a few more states:
import React, { useState } from 'react';
const ProductList = () => {
const [products] = useState([{ id: 1, name: 'Fortnite' }]);
const [cart, setCart] = useState([]);
const addToCart = (p) => {
const newCartItem = { ...p };
setCart([...cart, newCartItem]);
}
return (
<div>
<h2>Cart items</h2>
{cart.map(item => <div>{item.name}</div>)}
<h2>Products</h2>
{products.map(p => <div onClick={() => addToCart(p)}>{p.name}</div>)}
</div>
)
}
export default ProductList;
Above we are creating the states products
and cart
and in doing so we also get their respective change function setProducts
and setCart
. We can see in the markup we invoke the method addToCart()
if clicking on any of the items in our products
list. This leads to the invocation of setCart
, which leads to the selected product ot be added as a cart item in cart
. This is a simple example but it really showcases the usage of setState
hook.
The Effect hook is meant to be used to perform side effects like for example HTTP calls.
It performs the same task as life cycle methods componentDidMount
, componentDidUpdate
, and componentWillUnmount
.
Here is how we can use it:
import React, { useEffect, useState } from 'react';
const products = [{ id: 1, name: "Fortnite" }, { id: 2, name: "Doom" }];
const api = {
getProducts: () => {
return Promise.resolve(products);
},
getProduct: (id) => {
return Promise.resolve(products.find(p => p.id === id));
}
}
const ProductList = () => {
const [products, setProducts] = useState([]);
const [product, setProduct] = useState('');
const [selected, setSelected] = useState(2);
async function fetchData() {
const products = await api.getProducts();
setProducts(products);
}
async function fetchProduct(productId) {
const p = await api.getProduct(productId);
setProduct(p.name);
}
useEffect(() => {
console.log('use effect');
fetchData();
fetchProduct(selected);
}, [selected]);
return (
<React.Fragment>
<h1>Async shop</h1>
<h2>Products</h2>
{products.map(p => <div>{p.name}</div>)}
<h3>Selected product</h3>
{product}
<button onClick={() => setSelected(1)}>Change selected</button>
</React.Fragment>
);
}
export default ProductList;
Ok, a lot of interesting things was happening here. Let's start by looking at our usage of useEffect
:
useEffect(() => {
console.log('use effect');
fetchData();
fetchProduct(selected);
}, [selected]);
What we are seeing above is us calling fetchData
and fetchProduct
. Both these methods calls methods marked by async
. Why can't we just make the calling function in useEffect
async, well that's a limitation of hooks unfortunately.
Looking at the definition of these two methods it looks like the following:
async function fetchData() {
const products = await api.getProducts();
setProducts(products);
}
async function fetchProduct(productId) {
const p = await api.getProduct(productId);
setProduct(p.name);
}
We see above that we are calling getProducts
and getProduct
on api
which both returns a Promise. After having received the resolved Promise, using await
we call setProducts
and setProduct
that are functions we get from our useState
hook. Ok, so this explains how useEffect
in this case acts like componentDidMount
but there is one more detail. Let's look at our useEffect
function again:
useEffect(() => {
console.log('use effect');
fetchData();
fetchProduct(selected);
}, [selected]);
The interesting part above is the second argument [selected]
. This is us looking at the selected
variable and let ourselves be notified of changes, if a change happens to selected
then we will run our useEffect
function.
Now, try hitting the bottom button and you will see setSelected
being invoked which trigger useEffect
because we are watching it.
Hooks replaces the needs for many life cycle methods in general so it's important for us to understand which ones.
Let's discuss Effect Hooks in particular and their life cycle though.
The following is known about it's life cycle:
Let's talk about when we access the DOM tree, to perform a side effect. If we are not using Hooks we would be doing so in the methods componentDidMount
and componentDidUpdate
. The reason is we cant use the render
method cause then it would happen to early.
Let's show how we would use life cycle methods to update the DOM:
componentDidMount() {
document.title = 'Component started';
}
componentDidUpdate() {
document.title = 'Component updated'
}
We see that we can do so using two different life cycle methods.
Accessing the DOM tree with an Effects Hook would look like the following:
const TitleHook = () => {
const [title, setTitle] = useState('no title');
useEffect(() => {
document.title = `App name ${title} times`;
})
}
As you can see above we have access to props
as well as state
and the DOM.
Let's remind ourselves what we know about our Effect Hook namely this:
Our effect is being run after React has flushed changes to the DOM — including the first render
That means that two life cycle methods can be replaced by one effect.
Let's now look at another aspect of the useEffect
hook namely that we can, and we should, clean up after ourselves. The idea for that is the following:
useEffect(() => {
// set up
// perform side effect
return () => {
// perform clean up here
}
});
Above we see that inside of our useEffect()
function we perform our side effect as usual, but we can also set things up. We also see that we return a function. Said function will be invoked the last thing that happens.
What we have here is set up and tear down. So how can we use this to our advantage? Let's look at a bit of a contrived example so we get the idea:
useEffect(() => {
const id = setInterval(() => console.log('logging'));
return () => {
clearInterval(id);
}
})
The above demonstrates the whole set up and tear down scenario but as I said it is a bit contrived. You are more likely to do something else like setting up a socket connection for example, e.g some kind of subscription, like the below:
onMessage = (message) => {
// do something with message
}
useEffect(() => {
chatRoom.subscribe('roomId', onMessage)
return () => {
chatRoom.unsubscribe('roomId');
}
})
Yes you can. With useState
and useEffect
the world is completely open. You can create whatever hook you need.
Ask yourself the following questions; Will my component have a state? Will I need to do a DOM manipulation or maybe an AJAX call? Most of all, is it something usable that more than one component can benefit from? If there are several yes here you can use a hook to create it.
Let's look at some interesting candidates and see how we can use hooks to build them out:
You could be creating things like:
a modal, this has a state that says wether it shows or not and we will need to manipulate the DOM to add the modal itself and it will also need to clean up after itself when the modal closes
a feature flag, feature flag will have a state where it says wether something should be shown or not, it will need to get its state initially from somewhere like localStorage
and/or over HTTP
a cart, a cart in an e-commerce app is something that most likely follows us everywhere in our app. We can sync a cart to localStorage
as well as a backend endpoint.
Let's try to sketch up our hook and how it should be behaving:
import React, { useState } from 'react';
function useFeatureFlag(flag) {
let flags = localStorage.getItem("flags");
flags = flags ? JSON.parse(flags) : null;
const [enabled] = useState(Boolean(flags ? flags[flag]: false));
return [enabled];
}
export default useFeatureFlag;
Above we have created a hook called useFeatureFlag
. This reads its value from localStorage
and it uses useState
to set up our hook state. The reason for us not destructuring out a set method in the hook is that we don't want to change this value unless we reread the whole page, at which point we will read from localStorage
anew.
Now we have create our custom Hook, let's take it for a spin:
import React from 'react';
import useFeatureFlag from './flag';
const TestComponent = ({ flag }) => {
const [enabled] = useFeatureFlag(flag);
return (
<React.Fragment>
<div>Normal component</div>
{enabled &&
<div>Experimental</div>
}
</React.Fragment>
);
};
export default TestComponent;
// using it
<TestComponent flag="experiment1">
We said earlier we weren't interested in changing the value exposed by useFeatureFlag
. To control our feature flags we opt for creating a specific Admin page. We count on the Admin page to be on a specific page and the component with the feature flag on another page. Result of that is were we to navigate between the two pages then we can guarantee that the feature flag component reads from localStorage
.
Imagine you have an admin page. On that admin page it would be neat if we could list all the flags and toggle them any way we want to. Let's write such a component:
import React, { useState } from 'react';
const useFlags = () => {
let flags = localStorage.getItem("flags");
flags = flags ? JSON.parse(flags) : {};
const [ flagsValue, setFlagsValue ] = useState(flags);
const updateFlags = (f) => {
localStorage.setItem("flags", JSON.stringify(f));
setFlagsValue(f);
}
return [flagsValue, updateFlags];
}
const FlagsPage = () => {
const [flags, setFlags] = useFlags();
const toggleFlag = (f) => {
const currentValue = Boolean(flags[f]);
flags[f] = !currentValue;
setFlags(flags)
}
return (
<React.Fragment>
<h1>Flags page</h1>
{Object.keys(flags).filter(key => flags[key]).map(flag => <div><button onClick={() => toggleFlag(flag)}>{flag}</button></div>)}
</React.Fragment>
)
}
export default FlagsPage;
What we are doing above is to read out the flags from localStorage
and then we render them all out. While rendering them out, flag by flag, we also hook-up ( I know we are talking about hooks here but no pun intended, really 😃 ) a method on the onClick
. That method is toggleFlag
that let's us change a specific flag. Inside of toggleFlag
we not only set the new flag value but we also ensure our flags
have the latest updated value.
It should also be said that us creating useFlags
hook have made the code in FlagsPage
quite simple, so hooks are good at cleaning up a bit too.
In this article we have tried to explain the background and the reason hooks where created and what problems it was looking to address and hopefully fix.
We have learned the following, hopefully;):
we can use one or both of the mentioned hook types and create really cool and reusable functionality, so go out there, be awesome and create your own hooks.