Our thoughts, knowledge, insights and opinions

Discover React Hooks

During the ReactConf 2018 the Hooks were presented and to be honest it have shaken up the React community. If you have not seen “React Today and Tomorrow and 90% Cleaner React With Hooks” video yet, then I encourage you to do so.

Introducing stateless functional components in React 0.14 allowed us to create a smaller reusable blocks of code. But if this component had to handle some more advanced logic, then we had to convert it into a class. Now by introducing React Hooks we are able to inject some logic into functional components and futhermore we can reuse this logic blocks in multiple components.

What are React Hooks?

React Hooks are functions that allow to “power up” your functional components by adding state or allowing them to cause side effects like fetching data from api (and many more!).

Soon they will be able to recreate all functionality of lifecycle methods in functional components, in short — they will replace class components in most cases. Right now, not all methods are available as Hooks: componentDidCatch and getSnapshotBeforeUpdate are still missing, but React team says that they are working on it, so we should expect them in near future.

It’s worth to get to know Hooks since:

In the longer term, we expect Hooks to be the primary way people write React components — Hooks FAQ

Why Hooks were created?

In React there was no easy way of reusing only component logic, component inheritance is strongly discouraged we could use component composition, practices like render props and higher order components but those are not perfect, they can make code really hard to read and debug due to “wrapper hell”.

Usually components start as small and overtime they getting bigger and bigger, complex ones are hard to follow, understand, debug and test. Hooks let you isolate component logic into smaller functions responsible only for certain pieces (eg. making requests).

The amount of boilerplate needed when you want to use lifecycle methods is also significantly reduced:

class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.state = {counter: 0}
    this.setCounter.bind(this)
  }

  setCounter(counter) {
    this.setState({counter})
  }
  render() {
    return <div>
      Current count: {this.state.counter}
      <br/>
      <button onClick={() => this.setCounter(this.state.counter+1)}>increase</button>
      <br/>
      <button onClick={() => this.setCounter(this.state.counter-1)}>decrease</button>
    </div>
  }
}

vs

function Counter() {
  const [count, setCount] = React.useState(0)
  return <div>
    Current count: {count}
    <br/>
    <button onClick={() => setCount(count+1)}>increase</button>
    <br/>
    <button onClick={() => setCount(count-1)}>decrease</button>
  </div>
}

And since we are working in one function scope, binding to this is no longer needed. Hooks are allowing us to reuse our component logic and create solutions for complicated problems in easy way.

But what if I like class components and for some cases they are the best - you may say. Fear not class components still will be there and all your knowledge about React concepts still will be valid, Hooks are just another method to combine them.

Default Hooks

Lets take a look on the most basic Hooks provided with React:

useState

This is the most straightforward hook, it allows us to keep information in components state. Unlike this.state it does not have to be an object and we can use primitive type:

function Counter() {
  const [count, setCount] = React.useState(0)
  return <div>
    Current count: {count}
    <br/>
    <button onClick={() => setCount(count+1)}>increase</button>
    <br/>
    <button onClick={() => setCount(count-1)}>decrease</button>
  </div>
}

It returns array with two elemets, state and function used to update state. On initial render it returns state is equal to value of first passed parameter.

You can try this one on CodePen.

useEffect

Second basic hook is useEffect, since functional components are executed on each render they shouldn’t cause any side effects like mutations, making api calls, subscriptions, this hook does address this shortcoming. By the default, function passed to useEffect is fired after every render. There is also a way to pass cleanup function, you have to only return it.

useEffect(() => {
  props.chatAPI.subscribe();
  return () => props.chatAPI.unsubscribe()
})

It’s worth remembering that previous effect is cleaned up before executing the next effect, so we should think about some way of execution limitation to avoid firing this functions on each update. We can do this by adding some conditions.

useEffect(() => {
  props.chatAPI.subscribe();
  return () => props.chatAPI.unsubscribe()
}, [props.chatAPI])

If we pass as second parameter array of variables, effect will be fired if one of them change, passing empty array [] will cause firing effect on first mount and before unmounting.

useContext

Context API uses render prop to provide context to component and usage looks more like this:

function Example () {
  return (<Context.Consumer>
  {context => <span>{context}</span>}
  </Context.Consumer>);
}

But if we will use Hook, code is much simpler:

function Example () {
  const context = useContext(Context);
  return (<span>{context}</span>);
}

Demo application

I’ve created a simple note taking application, you can check demo here and source code here. I think that the most interesting part of it is usage of useReducer Hook. This allows to not use redux in smaller apps (and maybe even in middle-sized ones).

Let’s take a look at useReducer that is used in demo application:

const [state, dispatch] = useReducer(function(state, action) {
  switch (action.type) {
    case 'NOTE_ADD':
      return [...state, Object.assign({}, { title: 'New Note', data: '' })];
    case 'NOTE_SAVE':
      state[action.index] = action.note;
      return [...state];
    case 'NOTE_DELETE':
      state.splice(action.index, 1);
      return [...state];
    default:
      return state;
  }
}, []])

As you can see, passed function is a pure function and looks like a ordinary reducer from redux. Second parameter is a initial state.

This hook returns array with current state at first position and dispatch function as second. Now we can change state by dispatching actions eg. dispatch('NOTE_ADD') will create new note. And all it is done without using any additional library like redux.

Summary

Although Hooks looks great and I’m excited to use them in production they come with some tradeoffs. You have to call Hooks at the top level of render function — this means no conditional Hooks because the order in which they are called matter.

function Example (props) {
  if (props.shouldUseEffect) {
    useEffect(() => {         // Wrong!!!
      console.log('test!')
    })
  }
  return (<span>example</span>);
}

Hooks also can only be used in functional components and other hook functions! But they can be mixed, you can put functional component inside class component and vice versa.

Overall I think that introducing Hooks into React is a step in the right direction. It probably take some time to establish some good practices and guidelines but some libraries already introduced Hooks integrations eg. react-i18next.

You like this post? Want to stay updated? Follow us on Twitter or subscribe to our Feed.