loading...

Hooked on React hooks

markusclaus profile image Markus Claus ・6 min read

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 hookand 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!

Discussion

pic
Editor guide
Collapse
pigozzifr profile image
Francesco Pigozzi

Great article!

Your explainations are clear and accessible.

I probably found a typo in your custom hook useFetchData inside the useEffect deps. You wrote [props.id] but maybe you meant to write [url] or something like this.

Cheers! 🦄

Collapse
markusclaus profile image
Markus Claus Author

You are correct. That was a copy and paste error. I missed that.

Thank you 😊

Collapse
httpjunkie profile image
Eric Bishard

Best kind of errors! IMHO..

Collapse
dimensi0n profile image
Erwan ROUSSEL

Thanks for this article 💪 I understand now how to use react hooks 🙃

Collapse
guico33 profile image
guico33

Your useFetchData example is wrong. Look at the function signature and how you call it.

Collapse
markusclaus profile image
Markus Claus Author

True, late night posting.. Never a good idea. Fixed it, thanks.

Collapse
motss profile image
Rong Sen Ng

Can we actually make multiple fetches with Promise.all inside an useEffect hook?

Collapse
httpjunkie profile image
Eric Bishard

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