Whenever I read about a new feature in React, I'm all over it. I try to understand what's going on and how I could apply this new feature to my projects.
Hooks
is one of the newest features of React. It was introduced in React 16.8 and it allows you to use state and lifecycle features in components which are not a class.
To use React Hooks you need to update React to version 16.8 or higher. Don't forget to also update react-dom
and, if you use it, react-hot-loader
. You will run into some strange errors if you don't update the hot loader.
The state hooks
As you may know in the pre 16.8 days, if you wanted to use state you needed to do something like this:
import React, {Component} from 'react';
class Something extends Component {
constructor(props){
super(props);
this.state = {
foo: 'bar'
}
this.setFoo = this.setFoo.bind(this);
}
setFoo(newFoo) {
this.setState({...this.state, foo: newFoo});
}
render() {
const {foo} = this.props;
return (
<div>
<span>{foo}</span>
<button onClick={this.setFoo('theRealFoo')}>Set Foo</button>
</div>
);
}
}
export default Something;
This example contains much code that is necessary to make the class in JavaScript work. To use this
in the right context, you need to give the function some special attention. The same example would look like this with hooks:
import React, {useState} from 'react';
const Something = () => {
const [foo, setFoo] = useState("bar");
return (
<div>
<span>{foo}</span>
<button onClick={setFoo('theRealFoo')}>Set Foo</button>
</div>
);
}
export default Something;
As you can see, the amount of code is significantly less. In this case useState
is a hook provided by React. It is a function which returns a state value and a function which is used to update it.
So there is no need for setState
anymore. React takes care of everything.
useState
takes a parameter which is the initial value for the state field.
Now sometimes state can become complex and you might need more than one variable for it. To manage this, you can use multiple useState
calls to create multiple state variables or you can use an object like you did with classes before.
I tried both ways and I think single variables are much easier to handle, because you don't need to do all the nested object merging as you might have done before.
Also React has another hook called userReducer
, which comes in handy when you have complex state handling. As in Redux, you use a function which takes the state and an action to update the state.
import React, {useReducer} from 'react';
const csReducer = (state, action) => {
switch(action.type) {
case 'foo': {
return 'foo';
}
case 'bar': {
return 'bar';
}
default: {
return state;
}
}
}
const ComplexSomething = (props) => {
const [someState, dispatch] = useReducer(csReducer, props.someState);
return (
<div>
<span>{someState}</span>
<button onClick={dispatch({type: 'foo'})}>Say Foo!</button>
<button onClick={dispatch({type: 'bar'})}>Say Bar!</button>
</div>
)
}
export default ComplexSomething;
You can see that the whole state handling is in the csReducer
. It takes the action and depending on the type, it returns another value for the state. You can also send a payload {type: 'someAction', payload: 'foobar!'}
to put maybe a fetched information into the state. If you return the state as it was given, React does not change the state and no rerenders are triggered.
As you can see, the state handling is pretty awesome. Either you do some easy stuff with useState
or you start to raise the stakes with useReducer
.
Lifecycle with hooks
Now that we can handle our state, you probably remember that I also mentioned you could do the lifecycle stuff with hooks. Let's talk about this.
There is a hook called useEffect
. It is called like this, because most things you do in the lifecycle methods, fetching data, adding events to the DOM or something like that, all this is called "side effects", hence useEffect
.
Let's have an example:
import React, {useState, useEffect} from 'react';
import Spinner from './Spinner';
const EffectComponent = (props) => {
const [pending, setPending] = useState(true);
const [product, setProduct] = useState({});
useEffect(() => {
setPending(true);
fetch(`https://myapi.api/product/${props.id}`).then((productData) => {
setProduct(productData);
setPending(false);
})
}, [props.id]);
if(pending === true) return <Spinner />
return (
<div>{product.name}</div>
)
}
export default EffectComponent;
At first we define our two state variables pending
and product
. Then we use useEffect
to fetch data. The function sets pending
to true and then loads data from an API. After the data arrived, it sets our product
state and then pending
to false, so our component will render.
The effect
is called every single time the component renders. If you have used componentDidMount
and componentWillUpdate
before you know that it is a pain to manage when data should load and when not. With useEffect
there is an easy solution to that. The second parameter is an array with variables. The effect
will only fire if the variables in the array have changed. In the above example I do [props.id]
, so the effect
will only fire every time props.id
changes.
You can also return a function in useEffect
which will be called when the component is unmounted. You can do some cleanup stuff there.
A new way to share stateful logic
In the last versions of React there were two ways to share stateful logic between components. Render props and higher order components. Both are fine and they work well, but getting the concept... Phuuu... I tried to explain both a hundred times to different people and maybe 50% got it.
When using React 16.8 you can use hooks to share stateful logic between components. This way is much simpler, because a hook is just a function... and everyone understands functions, right?
To share the logic, we can build our own custom hooks and call them, just like we call the prebuilt hooks.
import React, {useState} from 'react';
// This is the custom hook
function useFetchData(url, setData) {
const [pending, setPending] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setPending(true);
fetch(url).then((productData) => {
setData(productData);
setPending(false);
}).catch(error =>{
setError(error);
})
}, [url]);
return {pending, error};
}
const EffectComponent = (props) => {
const [product, setProduct] = useState({});
// and here we call it.
const {pending, error} = useFetchData(`https://myapi.api/product/${props.id}`, setProduct);
if(pending === true) return <Spinner />
return (
<div>
<span>{product.name}</span>
{error && <span class="error">{error}</span>}
</div>
)
}
export default EffectComponent;
Look at the example above. What I did there is take away the logic for fetching data and put it into a function, this function is the custom hook. The convention is to start every hook with use
and then your function name. Instead of rewriting the fetch logic, I now can simply use my useFetchData
hook.
I think this concept is much easier to grasp. You just put everything in a function, you call it hook
and you are done with it.
The rules of hooks
Now hooks have some specific things you need to know before you use them. The React people call them "Rules of Hooks".
In fact, there are only two:
1.) You can use hooks only in function components
There is one exception to this rule. You can use hooks in your custom hooks.
2.) You can't use hooks in any kind of loop, nested functions or within conditions.
The last one is because React remembers in which order you used the hooks and it uses this order to give you the correct data or do the correct stuff.
For example:
const [varA, setVarA] = useState(1);
const [varB, setVarB] = useState(2);
const [varC, setVarC] = useState(3);
This works fine. Whenever the component is called, the order of the hooks is the same. Whenever you use varC
, it is the value 3.
const [varA, setVarA] = useState(1);
if(varA === 2) {
const [varB, setVarB] = useState(2);
}
const [varC, setVarC] = useState(3);
This one is a problem. The moment varA
is 2, the order of the hooks changes and therefore things will go wrong.
There are others
Yes, there are others... in fact, there are a whole bunch of other React Hooks. But I think they are more edge case hooks you probably don't need that often, if ever. useMemo
could come in handy, if you want to do some heavy lifting within a function in the render process of your component. It takes a function and an array of values. The function only runs if the values in the array change. Otherwise it will return the memoized value. You can read up on memoization here.
One hook looks really interesting, though. useContext
I have to do some more testing with this one, but I think it will make use of Reacts' Context API much easier than it is right now.
Well, that's it for this post. I hope you learned something, I hope you enjoyed it at least a little bit. If you got tips or comments, feel free to post them.
Thank you for reading!
Top comments (8)
Great article!
Your explainations are clear and accessible.
I probably found a typo in your custom hook
useFetchData
inside theuseEffect
deps. You wrote[props.id]
but maybe you meant to write[url]
or something like this.Cheers! 🦄
You are correct. That was a copy and paste error. I missed that.
Thank you 😊
Best kind of errors! IMHO..
Thanks for this article 💪 I understand now how to use react hooks 🙃
Your
useFetchData
example is wrong. Look at the function signature and how you call it.True, late night posting.. Never a good idea. Fixed it, thanks.
Can we actually make multiple fetches with
Promise.all
inside anuseEffect
hook?I'm not sure the exact question here. But Hooks have a clear set of rules to follow. As well 'useEffect' can be used several times so you that you cad can properly group related code and seperate it from unrelated code. Co-location of your effects