DEV Community

Voltra
Voltra

Posted on

Hooks, composition, were they needed?

Intro

As a front-end developer but also an Engineer in Computer Science, hooks from React and the composition API from Vue have caught my attention.

So what are they? They designate an idiomatic way of abstracting data management and functionalities in mostly small reusable bricks.

And that's good right? So why the obnoxious title? I think one of my issues with this is that it promotes one bad thing while eliding the actual problem: poor software modelling and design.

The bad

Let's say your API returns data that looks like this:

{
  "status": 200,
  "data": {
    "entityType": "MyEntity",
    "myProperty": "yes",
    "translations": {
      "fr": {
        "myProperty": "oui",
      },
    }
  },
}
Enter fullscreen mode Exit fullscreen mode

You want to translate it, right? So you write a hook:

import { useMemo } from "react"
import { Locales } from "@/my/magical/helpers"

const defaultLocale = Locales.DEFAULT;

export const useApiTranslatableEntity = (entity, locale = Locales.DEFAULT) => {
  return useMemo(() => {
    if (
      typeof entity !== "object" ||
      entity === null ||
      typeof entity.translations === "undefined"
    )
    return entity;

    const { translations } = entity;

    const localeTranslationData = translations[locale] ?? translations[defaultLocale] ?? {};

    const defaultTranslationData = translations[defaultLocale] ?? {};

    const translatedEntity = {...entity, ...defaultTranslationData };

    return {...translatedEntity, ...localeTranslationData};
  }, [entity, locale]);
}
Enter fullscreen mode Exit fullscreen mode

The ugly

But then you realise that this is way too much for a hook, so you break it down in small functions:

import { useMemo } from "react"
import { Locales, extend, pluck } from "@/my/magical/helpers"

const defaultLocale = Locales.DEFAULT;

export const translateApiEntity = (entity, locale = Locales.DEFAULT, defaultLocale = Locales.DEFAULT) => {
    if (
      typeof entity !== "object" ||
      entity === null ||
      typeof entity.translations === "undefined"
    )
    return entity;

    const { translations } = entity;

    const localeTranslationData = pluck(locale).orDefault(translations[defaultLocale] ?? {}).from(translations);

    const defaultTranslationData = pluck(locale).orDefault(translations[defaultLocale] ?? {}).from(translations);

    const translatedEntity = extend(entity).with(localeTranslationData);

    return extend(translatedEntity).with(defaultTranslationData);
}

export const useApiTranslatableEntity = (entity, locale = Locales.DEFAULT) => {
  return useMemo(
    () => translateApiEntity(entity, locale, defaultLocale),
    [entity, locale]
  );
}
Enter fullscreen mode Exit fullscreen mode

Now it's much cleaner and much more manageable. But wait... the hook is basically just calling a regular function, I could have done that without hooks.

The good

And that's the thing. If every piece of code relies on hooks, you've probably done something wrong. And as such, if people tell you that hooks helps writing reusable code... you know they lie at least partially.

As seen above, the hook basically delegates all the data transformation to a regular JS function that can be used wherever in the app, you could even reuse it if you change frameworks.

Hooks are just one way of abstracting UI behavior. You could still do it by writing functions.

One thing they allow you to do is to group data and data management in one place.

Discussion (0)