Hopp til hovedinnhold

Hooks change everything, let's explore some fun patterns!

React recently released a preview of a new feature called Hooks. Up until now, function based components were only for show, you weren't able to add super advanced features with them alone. Then along came hooks...

Hooks are a feature where functional React components can be augmented in a way with special hook functions, in cooperation with the ReactDOM reconciler, that allows you to do things like add "lifecycle methods" and state. But Hooks aren't just adding state to functional components, they introduce new patterns, and even new ways of thinking about architecting our React applications!

Hooks Intro 📓

There are plenty of built in hooks to get started with, but for the purposes of being terse, I'm going to run through 3 of them. First up is useEffect:

It's been described in the open as a replacement for componentDidMount, componentDidUpdate and componentWillUnmount, but I don't think it's a direct analogy. To really get hooky with it, I like to think of useEffect as its own thing where you can do side effects in response to something changing, and have the opportunity to clean up anything you've done when it's time. Lets take a look:

import React, { useEffect } from "react";

function MyComponent() {
  useEffect(() => {
    // Do things here
    return () => {
      // Clean things up here
    };
  });

  return <p>Ho ho ho's</p>;
}

The beauty here is that useEffect also takes a second argument in addition to the effect function. That second argument is an array of dependencies. A dependency is a value that, if changed, will run that effect again. If not, it won't. See here:

import React, { useEffect } from "react";

function MyComponent({ externalVar }) {
  useEffect(() => {
    console.log("mount");
  }, []);

  useEffect(() => {
    console.log("externalVar changed!");
  }, [externalVar]);

  return <p>Ho ho ho's</p>;
}

In the example above, you'll see a few things. First, is one of my favorite parts of hooks: You can add as many as you want (providing they stay in the same order). Second, we have our first useEffect, which will only run once on the account of having an empty dependencies array. The second useEffect will only log when externalVar has changed, which lets you build some interesting and cleverly organized logic.

The next hook I'd like to talk about is useState. Wheeeeeeeee! This lets you add state to functional components. Pinch me. Let's take a look:

import React, { useState } from "react";

function StateComponent() {
  const [val, setVal] = useState({ count: 1, butts: false });

  useEffect(() => {
    setTimeout(() => {
      setVal((state) => ({
        ...state,
        count: 2,
      }));
    }, 1000);
  });

  return <p>Count: {val}</p>;
}

So in the above component, we see that useState's argument is the initial state. What it returns is an array of [value, valueSetter] that we use array destructuring to assign to our own local variables. Like useEffect you can have several mini states within your component as well, which is too much fun. Now, when setting our value, you see that I'm spreading the existing state in. There's a reason for that. Unlike setState that we are all used to in our class based components, a useState setter does not merge state, so that's on you. If I hadn't spread state, butts would have disappeared, and I'm personally not a fan of that.

Last I'd like to show useRef. It's not just for references to DOM elements/component instances in hook land. You can use it to reference anything's current value, which is helpful because sometimes scope gets tricky with hooks. Let's look at an absurdly simple example:

import { useRef } from "react";

function MyComponent() {
  const myRef = useRef(0);

  return <p>{myRef.current}</p>;
}

Think of refs as a place to put things you might have put on a class instance, but aren't quite comfortable putting in state. If you have a value in your component that oddly isn't updating, use a ref chief.

Custom Hooks 🏋️‍

So while the built in hooks are dope, and you can use them all on their own, the downright coolest thing about hooks is that they are basically just primitives that you can use to create new custom hooks. And it's pretty straight forward.

To create a custom hook, just create a function, and have it use hooks. You can return something, but that's optional. Let's check out a basic custom hook:

import { useState, useEffect } from "react";

export function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  function handleResize() {
    setWidth(window.innerWidth);
  }

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

  return width;
}

This is a hook that uses useState and useEffect to get the window size, keep it in state, return it, and update it when the window resizes. Check out how it's used:

import { useWindowWidth } from "./useWindowWidth";

function MyComponent() {
  const width = useWindowWidth();

  return <div style={{ width: width / 2 }}>The ol' half width div</div>;
}

While this is cool as hell, keep in mind that you can return anything from a hook, even components! My favorite new hooks pattern is one where you return a value and a component that controls it from the same hook. Lets dive into creating a custom component hook:

import { useState } from "react";

function useRange(initialVal, min, max) {
  const [val, setVal] = useState(initialVal);
  const setRange = (e) => setVal(e.target.value);
  return [val, <input type="range" min={min} max={max} onChange={setRange} />];
}

In the above example, we create a function that takes some options for our hook. We're creating a controlled range slider, and we let you specify the initial value, the min and max. We use useState to keep track of our value, and then we return an array, much like useState, but instead of a value and a setter, we return the value and a component that sets that value. Check out how it's used:

import React, { useRange } from "./useRange";

function MyComponent() {
  const [range, rangeComponent] = useRange(5, 0, 10);

  return (
    <>
      <p>Current Range: {range}</p>
      {rangeComponent}
    </>
  );
}

Conclusion 👋

I am real god damn excited about Hooks. They open up new, better (IMO) patterns for how we structure our component logic, and organize things delightfully by concern. I think so far we are just scratching the surface of the weird stuff we can do with them and I'm excited to see what the future holds. I've only shown 3 hooks in this article, but there are plenty more that you can check out in the links below.

I hope my article inspires you to go write some custom hooks, and helps you convince your boss to run 16.8 alpha in prod, like a gangster. So what are you waiting for? Get out there any try this new hotness!

Merry Christmas you filthy animals!

Did you like the post?

Feel free to share it with friends and colleagues