loading...
Cover image for Starting with React Hooks

Starting with React Hooks

stlnick profile image Nick ・5 min read

For just over a year now we've had access to this shiny new feature React Hooks. I'm sure most of us have at least heard of it.

For me I didn't know what it truly meant or how it compared to what was being done in React prior, honestly. So recently starting to work with React I've been learning about the Class-based approach as well as Hooks.

What Are Hooks?

Hooks are functions that let you “hook into” React state and lifecycle features from function components.

That is straight from ReactJS.org. Pretty simple, right? Seems so at least.

For me it was good to learn the Class-based approach first to understand where React was to move into using Hooks. It also made me appreciate Hooks that much more as I feel it's a little cleaner and more intuitive - that's personal preference though.

Class-based approach

When utilizing this approach we would define a class component that extends React.Component, that way we get access to the React lifecycle methods. This approach in itself has come a long way from the start.

Here's the traditional way we handled Class-based Components, again straight from ReactJS.org:

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

Now we can shorten that first bit where we had to call constructor(props) and super(props) and this.state = {} AND we had to bind any event handler we defined. It was just a bit cumbersome.

Here's the shorter Class-based Component syntax:

class Toggle extends React.Component {
  state = {
    isToggleOn: true
  }

  handleClick = () => {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

A little shorter and cleaner, right? All we have to do is just define the state object, set our properties and the only other change was converting handleClick into an arrow function - that way this will still reference our component.

Side Note: Functional Components

Functional Components are in a simple explanation just function that will return some jsx. A 'dumb component' if you will. They do not need to know state and just receive props to render their jsx correctly.

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

Quite simple.

Hooks

When utilizing Hooks our Functional Components take over the role our Class-based Components had. Hooks will not work inside a class - we must use our Functional Components.

For reference in the upcoming examples here is the previous Toggle Component refactored to use React Hooks:

const Toggle = () => {
  const [isToggleOn, setIsToggleOn] = useState(true)

  const handleClick = () => {
    setIsToggleOn(!isToggleOn)
  }

  render() {
    return (
      <button onClick={handleClick}>
        {isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

Commonly Used Hooks

In the Class-based approach we would have access to certain lifecycle methods that allowed us to update our application at the right times and perform operations at the right times.

useState()

This Hook took over for setState() in Class-based components.

const [isToggleOn, setIsToggleOn] = useState(true)

This useState() allows us to simultaneously set the state of isToggleOn with the argument provided to the method - here that is true - as well as get back a setter to change that piece of state in the future - here it's called setIsToggleOn.

The 'set' part of that is convention to make it clear that this function will set the piece of state called isToggleOn.

Call useState() for as many pieces of state you have in different calls and you'll have setters for each piece of state. It can make it much more clear what we're trying to change.

// Class-based setState()
handleClick = () => {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }
// Hooks
const handleClick = () => {
    setIsToggleOn(!isToggleOn)
  }

It's shorter and arguably more intuitive than having to type out all of the Class-based way.

useEffect()

Now when we needed to fetch data, setup subscriptions or explicitly change DOM elements we would use the lifecycle methods componentDidMount, componentDidUpdate and componentWillMount.

With useEffect we basically have those three methods wrapped up inside of just his one. Definitely check out the docs on useEffect as it goes way more in depth and gives you context on the best practices on how to split your logic.

Here, since I'm still reading those docs and wrapping my own head around this, I'm only going to provide a simple example that involves data fetching. This example is from a small practice project I recently did:

// The 'api.index()' method used in the following block of code that makes the call for data
export default {
  async index() {
    const res = await fetch('https://my-json-server.typicode.com/Claim-Academy-JS/products/products')

    return await res.json()
  }
}
  // Class-Based
  async componentDidMount() {
    const products = await api.index()
    this.setState({ filteredProducts: products, products })
  }

  // Hooks
  useEffect(() => {
    const fetchData = async () => {
      const products = await api.index()
      setFilteredProducts(products)
      setProducts(products)
    }

    // If the data hasn't been fetched then make the api call
    if (products.length === 0)
      fetchData()
  })

So in our Class-based Components we would use componentDidMount to ensure before we made an attempt to fetch data the component itself was rendered and then we would update it on receiving our data from the server. This lifecycle method runs just once as the Component only mounts once in its lifecycle.

useEffect will run when the Component mounts, each time it updates and right before it unmounts. That is why there's a piece of logic to ensure we don't attempt to re-fetch the data each time this Component updates.

Custom Hooks

Custom Hooks are basically what they sound like - your own defined Hooks that you can design to help you with application-specific tasks or maybe tasks you've found yourself doing a bunch and could be streamlined having your own Hook for that task.

Once again, Hooks are something I've been working with just a week or so at this point. I'm not the one to tell you how to develop your own Custom Hook because I haven't tried myself yet!

The docs on Custom Hooks do a good job explaining and have a great example to show a proper use case. It made sense to me reading but I'm not going to try and rehash that here as I haven't put it in practice yet.

There Is Much More to Hooks...

... than I can tell you! I'm only beginning to scratch the surface of how to use Hooks to their full potential.

Not to mention the Custom Hooks I've yet to experiment with if you check out the Hooks API Reference you'll see even more out-of-the-box Hooks like useContext, useReducer, useLayoutEffect and more!

These examples I have were just the first Hooks I've put to use and made me see the benefit and potential of utilizing Hooks moving forward.

Posted on by:

stlnick profile

Nick

@stlnick

A Full-Stack JavaScript Web Developer with hometown love.

Discussion

markdown guide