DEV Community

Andrew Givens
Andrew Givens

Posted on • Edited on

A Guide to Hooks in Terms of Classes

With the introduction of Hooks, it seems that the React team is trying to push developers away from using Class Components that implement state and lifecycle. Understanding how to use these new features may prove to be a little bit challenging to someone who is so used to Class Components. Hopefully this guide will help bridge the gap between how hooks are used to replace classes.

For this I will just be going over the basic hooks: useState and useEffect.

useState

So, let's start with the easier of the two. The useState hook replaces, well, state. Pretty obvious there. The only kicker is how it allows us to interact with state. Previously, our state was a class variable of our component.

class MyComponent extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = { count: 0 }
  }
}

Which could then simple be accessed with this.state and mutated with React's this.setState() method.

class MyComponent extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = { count: 0 }
  }

  render() {
    const { count } = this.state;

    return (
      <button type="button" onClick={() => this.setState({ count: count + 1})}>
        Add One
      </button>
    )
  }
}

With hooks, instead of our state being a single object that is accessed with a single method, each piece of state appears to us as individual components.

const MyComponent = () => {
  const [count, setCount] = React.useState(0);
  const [myName, setMyName] = React.useState('Andrew');
}

The value that is passed to useState is the value state will be initialized with, and, as you could probably guess, setCount(count + 1) would increment my count state variable by 1 and trigger an update of MyComponent.

The cool part with the use of hook's state is how it can interact with the useEffect hook.

useEffect

The useEffect hook has a little bit more to it than useState. Remember those awesome lifecycle methods we used to use?

class MyComponent extends React.PureComponent {
  constructor(props) {
    this.state = { channelId: 12345, message: '' }
  }

  this.componentDidMount() {
    discordClient.login(token);
  }

  this.componentDidUpdate() {
    { channelId, message } = this.state;
    discordClient.channels.get(channelId).send(message);
  }

  this.componentWillUnmount() {
    discordClient.destroy();
  }
}
const MyComponent = () => {
  const isInitialMount = React.useRef(true);
  const [channelId, setChannelId] = React.useState(12345);
  const [message, setMessage] = React.useState('');

  React.useEffect(() => {
    discordClient.login(token);

    return function cleanup() {
      discordClient.destroy();
    }
  }, []);

  React.useEffect(() => {
    if (isInitialMount.current) {
      isInitialMount.current = false;
      discordClient.login(token);
    } else {
      discordClient.channels.get(channelId).send(message);
    }

    return function cleanup() {
      discordClient.destroy();
    };
  }, [message]);

  this.componentDidUpdate() {
    { channelId, message } = this.state;
    discordClient.channels.get(channelId).send(message);
  }
}

Now, let's mash them all into a single function useEffect(). By default this acts as componentDidMount and componentDidUpdate. So, something like this would run on mount and each time the component updates.

const MyComponent = () => {
  React.useEffect(() => {
    console.log('yeehaw');
  });
}

If you want something to run on willUnmount, simple return a function from your useEffect like so...

const MyComponent = () => {
  React.useEffect(() => {
    console.log('yeehaw');

    return function unmount() {
      console.log('componentWillUnmount');
    };
  });
}

Whenever the component will unmount from the dom, React will execute the code in the function returned by useEffect. Easy enough, right?

Here comes the cool part, you know how in redux you can subscribe to certain elements of your store. Well, with useEffects you can do the same thing with your state.

Let's say we have a couple state variables, but we only want this effect to happen when one of them updates. Easy peasy.

const MyComponent = () => {
  const [howdy, setHowdy] = React.useState('partner');
  const [count, setCount] = React.useState(0);

  React.useEffect(() => {
    console.log('yeehaw');
  }, [count]);
}

This useEffect will now only run whenever the value of count changes.

Say you want a useEffect to only run on mount, just pass it an empty Array as the condition.

const MyComponent = () => {
  const [howdy, setHowdy] = React.useState('partner');
  const [count, setCount] = React.useState(0);

  React.useEffect(() => {
    console.log('yeehaw');
  }, []);
}

Now, lets try to re-implement that mock discord bot with a useEffect. We want the client to login on mount, send a message when the message updates, and the destroy the client on willUnmount. In order to make the client login only on mount, we'll have to utilize a useRef to determine if it's the initial mount. This is the suggested implementation by the React team.

const MyComponent = () => {
  const isInitialMount = React.useRef(true);
  const [channelId, setChannelId] = React.useState(12345);
  const [message, setMessage] = React.useState('yeehaw!');

  React.useEffect(() => {
    if (isInitialMount.current) {
      isInitialMount.current = false;
      discordClient.login(token);
    } else {
      discordClient.channels.get(channelId).send(message);
    }

    return function cleanup() {
      discordClient.destroy();
    };
  }, [message]);
}

Now get to hookin'!

Patrick Star playing Hooky

Top comments (0)