Melody Documentation
  • Why Melody?
  • Quickstart
    • What is Melody?
    • Tutorial
    • Installation
  • Templates
    • Twig
    • Mounting Components
    • Keys and Loops
    • Attributes
    • CSS Classes
    • Inline Styles
    • Refs
    • CSS Modules
  • Melody-Streams
    • Basic concept
    • A Step-by-Step Walkthrough
  • MELODY-STREAMS API
    • createComponent
    • The component function
  • melody-hooks
    • Overview
    • Rules of Hooks
    • Event Handling
    • Separation of Concerns
  • melody-hooks API
    • useState
    • useAtom
    • useEffect
    • useRef
    • useCallback
    • useMemo
    • useReducer
    • usePrevious
  • melody-component
    • Overview
  • melody-hoc
    • Overview
Powered by GitBook
On this page
  • Syntax
  • Reducing dependencies
  • What you don't want to do...

Was this helpful?

  1. melody-hooks API

useCallback

The useCallback hook is used to avoid re-creating functions during every execution of the hook. That helps to reduce the need to rebind event handlers and to create a more stable API from your own hooks.

Syntax

useCallback(callbackFunction: Function, cacheKeyProperties: Array<Any>): Function

useCallback accepts a function and, just like useEffect, an array of cache keys which determine when to re-create the callback.

function useToggleButtonState({ handleClick }) {
    const [isToggled, setToggled] = useState(false);
    const handler = useCallback(event => {
        handleClick(event);
        // when using an updater function to mutate the state,
        // we will always operate on the latest value and won't
        // need to depend on it
        setToggled(toggled => !toggled);
        // we rely on the handleClick prop which could change
        // thus we need to specify it in the array
    }, [handleClick]);
    return {
        handler,
        isToggled
    };
}
<button
    class="{{ isToggled ? 'selected' : 'not-selected'}}"
    onclick="{{ handler }}"
>Toggle me</button>

In the example above the callback will always be the same as long as the handleClick property stays the same. We express that dependency by including it in the cacheKeyProperties array.

As a consequence, Melody will only need to attach and remove the event handler when handleClick is changed.

Reducing dependencies

Given an empty array, useCallback will always return the very first function it has ever received. That's the ideal case that you should always try to achieve. The updater function as well as useAtom can help you achieve this goal. However, sometimes, like in the previous use case, you will need to rebind because of changing event handlers. That can be avoided by embracing the platform more strongly and by using the native event dispatching mechanism instead.

function useToggleButtonState() {
    const [isToggled, setToggled] = useAtom(false);
    const rootElement = useRef(null);
    const handler = useCallback(event => {
        event.stopPropagation();
        // instead of dealing with passed in event handlers, faking our own
        // event system, we can just dispatch a native event and leverage the
        // platform to deliver interesting events to anyone who wants to listen
        // see: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
        // for reference
        rootElement.dispatchEvent(new CustomEvent(
            'toggled',
            { detail: isToggled() }
        ));
        // the accessor function provided by useAtom also
        // allows us to avoid the dependency
        setToggled(!isToggled());
    }, []);
    return {
        rootElement,
        handler,
        isToggled: isToggled()
    };
}
<button
    ref="{{ rootElement }}"
    class="{{ isToggled ? 'selected' : 'not-selected'}}"
    onclick="{{ handler }}"
>Toggle me</button>

We wanted to keep the example close to the original but of course this would've been a very good chance for us to migrate from onclick style event handlers to a proper ref handler to achieve a cleaner and simpler implementation.

What you don't want to do...

Since hooks are generally a very new concept and lots of material is being written about them, there's also a very high chance that you'll face some very bad examples.

function useToggleButtonState({ handleClick }) {
    const [isToggled, setToggled] = useState(false);
    const handler = useCallback(event => {
        handleClick(event);
        // This is the critical mistake
        setToggled(!isToggled);
    }, [isToggled, handleClick]);
    return {
        handler,
        isToggled
    };
}

In that example the callback depends on the toggled state. Thus, every time the user actually toggles the button, the callback will need to be re-created.

How to avoid: Reduce dependencies as much as you can. Any dependency that is not strictly necessary is a dependency too much.

PrevioususeRefNextuseMemo

Last updated 5 years ago

Was this helpful?