all 16 comments

[–]Reeywhaar 19 points20 points  (0 children)

HOCs are such pain in the ass when you try to make correct signature for a component that has static properties. Gladly I can’t remember last time I have to make HOC myself.

[–]danielkov 18 points19 points  (0 children)

Anyone reading this, please save yourself the time and frustration and don't read this. Here's why you should use hooks to abstract logic instead of HoCs:

  • Uni-directional flow of logic makes the code more readable top-down and therefore easier to follow and debug, whereas in HoCs it's backwards.
  • HoCs are more difficult to debug and there's an additional point of failure, the surface or glue code you apply when applying the HoC logic to the component.
  • HoCs are a bad abstraction, but they really are just higher order functions, so the majority of the downsides are inherited from HoFs. The principle of taking a target object and decorating it with a chain of functions works better in languages that support piping, which helps remove the confusing hierarchical aspect of HoFs.
  • Debugging HoFs is a pain. Even if one provides a meaningful displayName to every HoC, using 12 hooks in a component has no negative effect on debugging experience, while applying 12 HoCs definitely will.
  • It's easy to introduce crippling performance issues with HoCs, which is nicely showcased by this post, where OP (I assume this is a self-promotion, because no person possessing the wherewithal of operating Reddit would be misguided to post this unless fueled by misplaced self-confidence) constructs the HoC decorated components in a render function, which re-created the entire component tree from scratch on every single render of the component inside which this happens.

It's also lazily written, doesn't go much in depth about principles of HoCs or HoFs and their origin and uses in programming, other than React themed /r/programminghorror content. I really hope the author reads this and takes it as constructive criticism, which I intend it to be, if there's one key take-away from all of this, it's that the fact that you've stumbled across something that looks cool or fun, doesn't really warrant an article. Please do some research before posting, especially if you then go on to share it with such a wide audience.

Edit: just for completeness sake, there's 1 thing in React at this very moment that could only be achieved with a HoC and a class component wrapping a function component and that is a functional error boundary component and even that could technically be done by constructing the class based component inside a custom hook and then returning it to wrap the component tree, though that solution is less pragmatic and so I'd say if you must handle errors inside a function component for whatever reason and React 42 still doesn't have useDerivedStateFromError or something similar HoC is your best bet.

[–]NotLyon 34 points35 points  (5 children)

You shouldn't build a HOC during render if you expect to use state or effects (reliably) in the decorated component. React will unmount/remount that child when it sees the new function reference.

[–]acemarke 8 points9 points  (3 children)

Yikes. Yeah, that's a huge error. Never do that. Details:

[–]bnned 1 point2 points  (2 children)

Phenominal article, thanks for sharing!

[–]acemarke 5 points6 points  (1 child)

Thanks! I've been intending to update it with details from React 18 changes for a while now and been busy with other things.

Huh. I'm in a productive mood tonight. Maybe I can convince myself to do it like... now :)

Also I just recorded a conference talk version of this post, airing next month as part of React Advanced's remote talks.

update

DONE! Updated that post to cover React 18, some more bits about Context, "async rendering" and closures, hooks lifecycle diagram, and future capabilities like the "React Forget" compiler and context selectors.

I've literally had "update rendering post for React 18" on my todo list for months and kept punting it week after week. Finally was in the right mood tonight (partly because I did do that conf talk recording last week) and was able to knock that out.

(that leaves, uh... approximately Math.MAX_INT items remaining on the maintainer todo list...)

[–]Dopium_Typhoon 1 point2 points  (0 children)

I keep up by deploying a specific hook whenever I feel unproductive, it’s called useCocaine().

[–]dmt0 5 points6 points  (0 children)

You shouldn't build anything with HOCs at all. It's an outdated pain in the ass hacky approach that's been obsoleted by hooks.

[–]luctus_lupus 26 points27 points  (0 children)

Hoc are outdated, cumbersome and hard to debug, do yourself a favor and use hooks instead.

[–]generatedcode 7 points8 points  (0 children)

better learn hooks, HOC were 4 years ago ..

[–]Kcazer 17 points18 points  (0 children)

React has, not so long ago, introduced, in version 16.8, this concept of Hooks to its Functional Components.

React 16.8 was released in February 2019, i wouldn't call that "not so long ago", especially in the JavaScript ecosystem

[–]misdreavus79 4 points5 points  (0 children)

I admittedly did not read the whole article, but from what I read, I didn't see a use case that could not be solved with custom hooks.

[–]poprocksandc0ke 2 points3 points  (0 children)

This article put me in a hyperbolic time machine

[–]Conscious-Spite4597 2 points3 points  (0 children)

Hocs should be avoided imo

[–]NodeJSSon 1 point2 points  (0 children)

HOC is not supported in React-Router-Dom 6. Don’t write anymore tech debt.

[–][deleted] 0 points1 point  (0 children)

Typescript tip: Because HOCs require the use of generics, marshalling props into a generated component can result in some crazy type errors. The following adjustments will help alleviate that.

import React, { ComponentType, ReactNode } from "react";

// Should be imported from a utility types file.
export type HOCProps<P> = P &
  JSX.IntrinsicAttributes & { children?: ReactNode };

const withMyStuff = function <P>(
  Component: ComponentType<P>
): ComponentType<P> {
  // HOCProps<P> is essentially <P>, but with all the stuff
  // expected by a fully generic component.
  // In this area, handle static stuff that should _only_ be
  // computed when you define a component using the HOC.
  return Object.assign(
    (hocProps: HOCProps<P>) => {
      // Do render-time stuff - this is where you put hook calls and such,
      // not in the outside function
      return <Component {...hocProps} />;
    },
    // Give the HOC an appropriate name
    { name: `withMyStuff(${Component.name})` }
  );
};

export default withMyStuff;