Write Custom Hooks in React
Tags: react, javascript
Published: 2020-02-02 15:41:39

Hooks are a relatively new way (React v16.8.x and up) to add state and lifecycle to functional components. Before hooks, you needed to use a class to have these same features. However, using classes in Javascript has its own set of issues:

- Some new devs might not have an OO background
- What's `this` for again?
- private vs public vs static???
- More complicated to share functionality
- Transpilers will convert classes to regular functions anyways

I've noticed that many developers prefer writing components as functional components as opposed to classes. They would then convert to a class once state was needed. Well, you don't need to do that anymore.

![Carleton Dance](https://media.giphy.com/media/pa37AAGzKXoek/giphy.gif)

## My Most Commonly Used Hooks

The built-in hooks that I use most often are:

- useState
- useReducer
- useEffect

### useState

`useState` is used to create state properties for your component. It's very similar to `this.state` in a class component.

```js
class TodoComponent extends React.Component {
  state = {
    content: ''
  }
  ...
}
// OR
class TodoComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      content: ''
    }
  }
  ...
}

// and what we like
function TodoComponent() {
  const [content, setContent] = React.useState('');
  ...
}
```

The variable `setContent` in the functional component above is the state updater function. It works like `this.setState`, and updates the `content` state and rerenders the component.

React.useState always returns an array with two items, the `state` variable as the first item, and the updater function as the second item. I highly recommend naming the updater function as `set<Name of state var>`. This will keep things consistent in your project.

### useReducer

`useReducer` is kinda like a more powerful `useState`. Why use `useReducer`?

- You have a lot of state props on your component
- You really like Redux's reducers

If your component has more than one or two state properties, you may prefer to create those props with `useReducer` over `useState`. It may be easier for you to manage a single `dispatch` function that takes a type and payload which will update your components state, than it is to have a bunch of individual state updater functions.

```js
const initialState = {
  name: '',
  address: '',
  city: '',
};

// Just like in Redux
function userReducer(state, action) {
  switch (action.type) {
    case 'SET_NAME':
      return {
        ...state,
        name: action.payload,
      };
    case 'SET_ADDRESS':
      return {
        ...state,
        address: action.payload,
      };
    case 'SET_CITY':
      return {
        ...state,
        city: action.payload,
      };
  }
}

function UserComponent() {
  const [state, dispatch] = React.useReducer(userReducer, initialState);

  return (
    <div>
      <h1>{state.name}</h1>
      ...
    </div>
  );
}
```

### useEffect

`useEffect` handles rerendering of your component based on state or property updates. It is also what you use to handle side effects, aka fetching data from an API.

```js
function UserComponent() {
  const [userId, setUserId] = React.useState();
  React.useEffect(() => {
    async function fetchToken() {
      try {
        const response = await axios({
          method: 'GET',
          url: `${API_PATH}/user/${userId}`,
          withCredentials: true,
        });
        setToken(get(response, 'data.trustedTicket'));
      } catch (error) {
        console.error(error);
      }
    }

    fetchToken();
  }, [userId]); // Run the useEffect code when `userId` changes

  return (
    ...
  )
}
```

## Custom Hooks

Now that you have more of an understanding on some very common hooks, lets create our own custom hook. First, we need to name the hook.

```js
function useTodos() {}
```

Please start every hook with the word `use`. This is for your own good. The React team has an ESLint plugin that is very helpful at keeping us from messing up our hooks.

> We provide an ESLint plugin that enforces rules of Hooks to avoid bugs. It assumes that any function starting with ”use” and a capital letter right after it is a Hook.

Now that we have a hook defined, we can add in some state and functionality.

```js
let nextTodoId = 0;
function useTodos(initialTodos = {}) {
  const [todos, setTodos] = React.useState(initialTodos);

  const addTodo = content => {
    const id = ++nextTodoId;
    setTodos({
      ...todos,
      [id]: {
        content,
        completed: false,
        id,
      },
    });
  };
  const toggleTodo = id => {
    setTodos({
      ...todos,
      [id]: {
        content: todos[id].content,
        completed: !todos[id].completed,
        id,
      },
    });
  };
  return [todos, addTodo, toggleTodo];
}
```

Custom hooks can take parameters just like any other function. Here i'm passing an **initialTodos** object that will default to an empty object if undefined.

I've add two updater functions `addTodo` and `toggleTodo` that both update the `todos` state property.

I'm returning an array of values, just like the `useState` and `useReducer` hooks.

```js
...
  return [todos, addTodo, toggleTodo];
```

## Using the custom hook

You use the custom `useTodos` hook just like any other hook.

```js
function MyComponent() {
  const [todos, addTodo, toggleTodo] = useTodos();

  return (
    <>
    <AddTodo addTodo={addTodo}>
    <TodoList toggleTodo={toggleTodo} allTodos={todos}>
    </>
  )

}
```

We're passing the `useTodos` hook values to the **<AddTodo />** and **<TodoList />** components. When `addTodo` is called, for example, <MyComponent /> will rerender, since we call a state updater function within `addTodo`. The `todos` object will have updated, and that means the component needs to rerender.

Well I hope this has been helpful for you if you're getting into hooks. Let me know if you have any questions about the above code. Have fun coding 😊