Hooks Are the Jam

I am absolutely THRILLED to launch this year's JavaScript January with Ken Wheeler's article on hooks.

If you've been following the React scene lately, you've probably heard about "hooks". If you havent, thats fine, because I'm going to tell you all about them right now.

Hooks are a feature proposal in ReactJS, originally shown at React Conf 2018 and available in React alpha builds, that let you build rich applications with functional components only. If you aren't an avid React'er, let me explain:

Here is how React components generally work now: You have either a class based component or a function based component. A function component just returns a JSX template, but can be augmented a bit with libraries like Recompose. Lets check one out:

    const MyComponent = function (props) {
      const { name } = props;

      return (
        <p>Hi my name is {name}</p>;
      )
    }

Up until now, you've been able to do a bit in the function body, but these function components were colloquially described as SFCs or stateless functional components, in order to distinguish them from their more full featured class based counterparts.

Class based components on the other hand provide things like lifecycle methods and component local state. They have typically been the go to for more complex component types. Here's an example of what we've been doing up until this point:

    class MyComponent extends React.Component {
      state = {
        count: 0,
      };
    
      increment = () => {
        this.setState(state => ({
          count: state.count + 1,
        }));
      };
    
      componentDidMount() {
        console.log('I mounted');
      }
    
      render() {
        const { count } = this.state;
        return (
          <div>
            <p>Count: {count}</p>
            <button type="button" onClick={this.increment}>
              Increment
            </button>
          </div>
        );
      }
    }

Enter Hooks

The new feature proposal for hooks now lets us do the kinds of things you are seeing above, but with our regular function components. So you might wonder, why would we want to use functions instead of classes. It's actually a lot more than hipster JS class hate. The new hooks style provides new opportunites for optimization that previously classes had prevented. But whats more, the new style is actually a completely different mindset when it comes to creating your components.

You see, classes with lifecycle methods/state to hooks conversion is not a 1 to 1 ordeal. There are certainly ways you can accomplish the things you were doing prior with lifecycle methods, but we aren't getting like componentDidMount's in our hook functions. We instead get useEffect and it is a versatile primitive that can be used to replace componentDidMount, but also does so much more.

It's hard to put into words why hooks are better so lets just convert the above class example into a hook based component:

    import React, { useState, useEffect } from 'react';
    
    function MyComponent(props) {
      let [count, setCount] = useState(0);
    
      useEffect(() => {
        console.log('I mounted');
      }, []);
    
      const increment = () => {
        setCount(count => count + 1);
      };
    
      return (
        <div>
          <div>
            <p>Count: {count}</p>
            <button type="button" onClick={increment}>
              Increment
            </button>
          </div>
        </div>
      );
    }

As you can see above, the way we are architecting our logic is pretty different. As you can also see, it looks a bit cleaner. Heres why: We are skipping all instance/this references, we operate in a single root component scope, and we aren't repetively grabbing things off the props/state. I could be a silly goose, but this feels easier on the eyes.

Now that we've seen what hooks look like, lets check out whats actually happing there. We use two hooks here, useState and useEffect. Lets explore how each of these works.

useState

If you are coming from class components, you are probably used to using setState. This is a setter for a single component local state. In hooks-land, we instead use a function called useState. It takes the initial state as an argument and returns an array. Our first order of business is destructuring that array into the first and second indexes, the value and the setter, respectively.

    const [state, setState] = useState(initialState);

One of my favorite parts of hooks is that unlike class state or lifecycle methods, you can have as many of them as you want, AS LONG AS THEY AREN'T WRAPPED IN CONDITIONALS AND EXECUTE IN THE SAME ORDER EVERY TIME. Seriously, this is the rule of hooks, and its worth abiding by, and not too much trouble to conform to. See you can have multiple states:

    const [count, setCount] = useState(0);
    const [active, setActive] = useState(false);

Also because it's just array destructuring, you can name the things whatever you want:

    const [butts, fartMethod] = useState('crap');

The world is your oyster.

One thing worth mentioning is, unlike setState, the new state is not merged into the old state, so if you have an object state with multiple keys, be sure to spread it manually (heyo):

    const [state, setState] = useState({ count: 0, active: false });
    
    setState(state => ({
      ...state,
      count: state.count + 1,
    }));

useState is internally a convenience wrapper around another hook called useReducer, and you can check out the docs to see how you can make your own local little redux.

useEffect

The second hook we saw in our example was useEffect. The docs state that this can be thought of as componentDidMount, componentDidUpdate and componentWillUnmount all in one. I like to think of it as a way to run effects, and not a direct analog to those lifecycle methods.

Let explore it's API surface: useEffect takes two arguments. The first is a function that gets run. The second the is an array of "dependencies", which are values that, if changed, make the function run.

    useEffect(() => {
      console.log('I run on mount');
    }, []);

As you can see above, the dependencies argument is just a blank array. Heres why: useEffect runs at very least one time. If you pass an empty array as the dependencies, it will only run once. If you don't pass a second argument, it runs every time the component updates.

    useEffect(() => {
      console.log('I run on mount and update');
    });
    
    useEffect(() => {
      console.log('I run on mount');
    }, []);
    
    useEffect(
      () => {
        console.log('I only run on mount, and when props.count changes');
      },
      [props.count]
    );

Like the rest of the hooks, you can add multiple, and this opens up some fun architectural organization. Any side effect you have run is wrapped in a useEffect that specifies the dependencies for it running, and has a... wait for it... side effect of organizing your code a bit better.

So typically, if you have imperitive calls you make on mount, you'll see things like subscriptions to events. The React squad knows this, and this is why you can return a function from your first arg function that will run on un-mount, allowing you to clean up any subscriptions/effect stuff. Check it out:

    useEffect(() => {
      window.addEventListener('resize', handleResize);
    
      return () => {
        window.removeEventListener('resize', handleResize);
      };
    }, []);

Custom Hooks

My favorite part of hooks is that they are basically just primitives that can be used to craft even cooler hooks by mixing them together in the context of a function. A custom hook is really just a function that uses hooks. Lets build one:

    function talkShitOnMount() {
      useEffect(() => {
        console.log('your mom plays badminton');
      }, []);
    }
    
    function App() {
      talkShitOnMount();
    
      return <p>lol</p>;
    }

So thats an example of the sort of hook that doesn't return anything, but we can return things just like useState does as well. We can even return components!

    function useToggleButton() {
      const [active, setActive] = useState(false);
    
      const handleClick = () => {
        setActive(active => !active);
      };
    
      return [
        active,
        <button type="button" onClick={handleClick}>
          {active ? 'On' : 'Off'}
        </button>,
      ];
    }
    
    //...
    
    function App() {
      let [active, Button] = useToggleButton();
    
      return (
        <div>
          <Button />
          {active && <Content />}
        </div>
      );
    }

It gets even more powerful when you link them together to do something like control effects:

    function App() {
      let [active, Button] = useToggleButton();
    
      useEffect(
        () => {
          if (active) {
            playMusic();
          } else {
            stopMusic();
          }
        },
        [active]
      );
    
      return (
        <div>
          <Button />
        </div>
      );
    }

Wrap up

I hope I've succeeded in piquing your interest about hooks. I think it's a really great feature that will change the way we all work, and will be inspirational for future patterns for years to come. If you want to get started, head over to the React docs site and see ALL the available hooks, what they do, and give it a try today.

 

Want to read more? Learn how to create a MongoDB app with React and Azure Cosmos.

The contributors to JavaScript January are passionate engineers, designers and teachers. Emily Freeman is a developer advocate at Kickbox and curates the articles for JavaScript January.