Write Custom Hooks in React

Learn about common React hooks and creating a custom hook

Sun, 02 Feb 2020

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.

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.

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.

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.

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.

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.

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.

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

Using the custom hook

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

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 and components. When addTodo is called, for example, 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 😊

Loading...
marques woodson

Marques approves this article.