DEV Community

Cover image for Destructuring the Fundamentals of React Hooks
Colby Fayock
Colby Fayock

Posted on • Updated on • Originally published at colbyfayock.com

Destructuring the Fundamentals of React Hooks

Hooks have become a pretty powerful new feature of React, but they can be intimidating if you’re not really sure what’s going on behind the scenes. The beauty is now being able to manage state in a simple (and reusable) manner within function components.

But why not just use a class? Without getting too far away from the topic, functions provide a more straightforward way to write your components, guiding you to write in a cleaner and more reusable way. Bonus: it typically makes writing tests easier.

There’s a lot of use cases for hooks, so I won’t dive into examples, but it shouldn't be too bad to get up to speed with a few quick lines.

Diving into the cookie jar

Here we have MyCookies, a function component, which we can consider our cookie jar. Let's say we want to internally keep track of how many cookies we have in the jar. With the new hooks API, we can add a simple line using useState to handle the job.

const MyCookies = () => {
  const [ cookies, setCookieCount ] = useState(0);
  ...
};
Enter fullscreen mode Exit fullscreen mode

Wait, how do we get cookies out of that?

If you think the above is magic and are wondering how the values in the array are getting set, you’ll need to understand the basics of array destructuring.

While destructuring an object will use the same key wherever you try to pull it from, arrays destructure using the order of the items within the array.

const [ one, two ] = [ 1, 2 ];
console.log(one); // 1
console.log(two); // 2
Enter fullscreen mode Exit fullscreen mode

While the above seems like it’s naming them in a particular order, it’s not as shown below:

const [ two, one ] = [ 1, 2 ];
console.log(two); // 1
console.log(one); // 2
Enter fullscreen mode Exit fullscreen mode

Without going too far down the technical rabbit hole, useState is a function that returns an array that we’re destructuring for use within our component.

What about the 0 inside the invocation of useState itself? That’s simply the initial value we’re setting the state instance to, so in this case, we’ll sadly start off with 0 cookies.

Actually use state

Once we have our destructured cookies and the setCookiesCount function, we can start interacting with the component’s local state like you might do using setState within a class component.

At render time, our cookies value will be that invocation of useState’s internal state value, similar to what you might see with this.state. To update that value, we can simply call setCookiesCount .

const MyCookies = () => {
  const [ cookies, setCookieCount ] = useState(0);
  return (
    <>
      <h2>Cookies: { cookies }</h2>
      <button onClick={() => setCookieCount(cookies + 1)} >
        Add Cookie
      </button>
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

If you’re more used to the class syntax, you might update state using this.setState looking something like this:

class MyCookies extends React.Component {
  constructor() {
    super();
    this.state = {
      cookies: 0
    }
  }
  render() {
    return (
      <>
        <h2>Cookies: { this.state.cookies }</h2>
        <button onClick={() => this.setState({cookies: this.state.cookies + 1})}>
          Add cookie
        </button>
      </>
    )
  }
}

Enter fullscreen mode Exit fullscreen mode

How to use effects

Often components need a way to create side effects that won’t necessarily interrupt the functional flow of a function component. Say we have the number of cookies we have saved on a server somewhere, we might want to fetch that count when the app loads.

const MyCookies = () => {
  const [ cookies, setCookieCount ] = useState(0);
  useEffect(() => {
    getCookieCount().then((count) => {
      setCookieCount(count);
    })
  });
  ...
};
Enter fullscreen mode Exit fullscreen mode

After the component renders, everything inside of useEffect will run, meaning any side effects originating from useEffect will only occur after the render is complete. That said, once useEffect does run, we fire getCookieCount and use our previous setCookieCount function to update the state of the component.

Hold up, there's something wrong...

There’s a gotcha in the code above though. That effect will run every time, essentially wiping out any new increments to our cookie value from our original Add Cookie button.

To get around this, we can set a 2nd argument to the useEffect function that allows us to let React know when to run it again. In our example above, setting that 2nd argument to an empty array will make it run only once.

const MyCookies = () => {
  const [ cookies, setCookieCount ] = useState(0);
  useEffect(() => {
    getCookieCount().then((count) => {
      setCookieCount(count);
    })
  }, []);
  ...
};
Enter fullscreen mode Exit fullscreen mode

In most cases though, you’ll want to pass an array of dependencies that when changed, will cause useEffect to fire again. Say for example, you’re fetching the count of a specific cookie type and want to get the count again if that type changes.

const MyCookies = ({cookieType = 'chocolate'}) => {
  const [ cookies, setCookieCount ] = useState(0);
  useEffect(() => {
    getCookieCount().then((count) => {
      setCookieCount(count);
    })
  }, [ cookieType ]);
  ...
};
Enter fullscreen mode Exit fullscreen mode

In the above code, any time our prop cookieType changes, React knows that we depend on it for our effect, and will rerun that effect.

Trying to make use of context

I’m not going to go into the details of React’s context API as that’s a little out of scope. However, if you’re familiar with it, the useContext hook let’s you easily make use of your context from within your function component.

import BasketContext from 'context';

const Basket = ({children}) => {
  return (
    <BasketContext.Provider value={basketItems}>
      <h1>My Basket</h1>
      { children }
    </BasketContext.Provider>
  );
}

// MyCookies.js
const MyCookies = ({cookieType = 'chocolate'}) => {
  const basketItems = useContext(BasketContext);
  ...
};
Enter fullscreen mode Exit fullscreen mode

In the above code, given our already created context, we can immediately “use” said context and collect the values passed into our context provider.

Cleaning your hooks

What makes hooks even more powerful is combining and abstracting them DRYing up your code in a cleaner way. As a quick last example, we can take our cookie examples of useState and useEffect and abstract them into their own use[Name] function, effectively creating a custom hook.

// useCookies.js
function useCookies(initialCookieCount) {

  const [ cookies, setCookieCount ] = useState(initialCookieCount);

  useEffect(() => {
    getCookieCount().then((count) => {
      setCookieCount(count);
    })
  }, []);

  function addCookie() {
    setCookieCount(cookies + 1);
    console.log('😍');
  }

  function removeCookie() {
    setCookieCount(cookies - 1);
    console.log('😭');
  }

  return {
    cookies,
    addCookie,
    removeCookie
  }
};

// MyCookies.js
const MyCookies = () => {
  const { cookies, addCookie, removeCookie } = useCookies(0);
  ...
};
Enter fullscreen mode Exit fullscreen mode

We were safely able to abstract our state logic and still utilize it to manage our cookies.

Plenty more to get hooked on

These are the basic 3 hooks React gives us, but there are many more they provide out of the box, all with the same underlying principles that the React documentation does a good job at explaining.

Get more content straight your inbox!

Originally published on April 17, 2019 at colbyfayock.com

Top comments (0)