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
  • Hooks are unconditional
  • Hooks don't loop
  • Hooks can be uniquely identified
  • Hooks start and end in Melody
  • The linter knows best

Was this helpful?

  1. melody-hooks

Rules of Hooks

The high expressiveness of hooks comes with a price. Their implementation within the JavaScript language mandates the introduction of a few rules that must be followed.

The following rules must be followed. Any mistake will cause your application to behave strangely or will introduce hard to find and hard to fix bugs.

Hooks are unconditional

The order and existence of hooks within a component must be the same for the entire lifetime of a component. That means that you can't wrap an if-statement around a hook or make it conditional in any other way. If it is there at some point, it must be always there.

function useToggleButton() {
    const [isSelected, setSelected] = useState(false); // Hook #1
    let highlight = false;
    // DO NOT EVER DO THIS!
    if (isSelected) {
        const [isArmed, setArmed] = useState(true); // Hook #2
        highlight = isArmed;
    }
    const handleClick = useCallback(/* ... */); // Hook #3
    return { isSelected, highlight, handleClick };
}

Hooks are backed by the current component and store their data in an indexed list - which is also why you must not use them in a conditional way. With the incredibly bad and wrong example above, Hook #2 is conditional. It might exist - or not. If it does exist, Hook #3 will use the index 3 for storing and retrieving its related data. If, however, Hook #2 does not exist, then Hook #3 will suddenly use index 2 for storing and retrieving its data - a slot that already belongs to Hook #2! You'd get the wrong data and your application would become unpredictable.

Thus, Hooks must always be used at the top-level of a function. They can't be used within if-statements, switch-statements, ternaries or anything else.

Hooks don't loop

For the same reason for which you must not use conditional Hooks, you also must not use them within a loop. You might have a situation where you think the order would always be predictable but please keep in mind that your application will grow and that new people will join. They might make a change that puts and end to your predictable order and the application would become unpredictable.

Thus, you must not use Hooks within a loop, regardless of whether it is a for-loop, while-loop, do-while-loop or even a goto (yes, JavaScript has a very similar concept).

Hooks can be uniquely identified

We strongly encourage you to split Hooks into separate single-purpose Hooks. However, since Hooks use quite a large degree of magic that makes them behave unlike any other function in JavaScript, it is absolutely necessary to make it obvious whether you are in a Hook context or not.

To achieve this, we prefix all Hooks with the word use. This also helps the linter identify Hooks and their usage, and helps to prevent errors.

// DO NOT DO THIS!
function getPerson() {
    const [firstName, setFirstName] = useState();
    const [lastName, setLastName] = useState();
    
    return {
        getFirstName() { return firstName; },
        getLastName() { return lastName; },
        getName() { return `${firstName} ${lastName}`; },
        setFirstName,
        setLastName
    };
}

In the above example, it'd be nearly impossible to identify that getPerson is a Hook itself. As a consequence, you or one of your colleagues might use the function outside of a Melody component or within a conditional or looping context, causing errors to appear throughout the application.

To avoid this situation, all Hooks must begin with the word use.

Hooks start and end in Melody

Hooks rely on a Melody component in the background which they use for storing and retrieving their data or for registering their callbacks with. Without that component in the background, they can't work.

// DO NOT EVER DO THIS!
function fibonacci(n) {
    const [cache] = useState({});
    if (n <= 1) {
        return 1;
    }
    if (cache[n]) {
        return cache[n];
    }
    const result = fibonacci(n - 1) + fibonacci(n - 2);
    cache[n] = result;
    return result;
}

The above implementation might be clever in that it uses a cache; however, it'd only ever work within a Melody components state hook - and it has the wrong name.

Thus, Hooks must only be used while the Components State Hook is executed.

The linter knows best

You can install the linter using the following command:

npm install eslint-plugin-react-hooks@next

And adapt your configuration like this:

// Your ESLint configuration
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error"
  }
}

We strongly recommend to use this plugin to protect against the errors we've mentioned on this page.

PreviousOverviewNextEvent Handling

Last updated 5 years ago

Was this helpful?

For the time being Melody shares the eslint plugin developed by the React team, .

eslint-plugin-react-hooks