1

I am using Hooks + Render Props in my Preact/React application. Instead of passing simple state to the render(), I am passing a component function that can be used by the user of this component.

Also, my component is Hybrid in a sense that that SortDropdown is not UI-Less like we typically have with Render Prop components. It provides some basic UI in the form of anchor element which triggers the actual drop down and shows the custom content via Render Props render function. This is how I have implemented it:

export function SortDropdown(props: SortDropdownProps) {
  const { render, onSelect, value } = props;

  const anchor = (ref: Ref<Element>, open: VoidFunction) => (
    <SortButton buttonElm={ref} onClick={open} />
  );

  const surfaceContent = (ref: Ref<any>, select: SortSelectFn) => {

    // NESTED INNER COMPONENT - IS IT RIGHT?
    function SortMenuItem(props: SortMenuItemProps) {

      const { children, field } = props;

      // THE HOOK IN QUESTION
      const [someState, setSomeState] = useState(false);

      const isSelected = value.key === field.key;

      const onClick = () => select(field);

      return (
        <SortMenuButton canTrigger={someState} selected={someState} onClick={onClick}>
          {children}
        </SortMenuButton>
      );
    }

    return (
      <div ref={ref}>{render(SortMenuItem, select)}</div>
    );
  };

  return (
    <BaseDropdown anchor={anchor} surface={surfaceContent} onSelect={onSelect as any} />
  );
}

Here I have two questions. First, does defining an inner nested component, here - SortMenuItem which is then passed to the render function violates the rules of Hooks? The component may be called n-times or may not be used at all by the calling component.

Second, is it a right practice to define a nested Higher-Order Component, here - SortMenuItem being an abstraction over SortMenuButton?

Harshal Patil
  • 17,838
  • 14
  • 60
  • 126
  • Am I missing something? I don't see a single hook in your code... You are just using normal vanilla js functions, in which case no, you do not violate any rules-of-hooks – Bill Metcalf Mar 06 '20 at 18:52
  • 5
    ^ is right, this has nothing to do with hooks, but defining components like this is not right, it does't violate anything but it also doesn't make any sense. – Adam Jenkins Mar 06 '20 at 18:52
  • Does this answer your question? [Is it an anti-pattern to define a function component inside the render() function?](https://stackoverflow.com/questions/52372040/is-it-an-anti-pattern-to-define-a-function-component-inside-the-render-functio) – Emile Bergeron Mar 06 '20 at 18:55
  • 1
    Just pass `value` to `SortMenuItem` as a prop and then you can move it outside - easy. – Adam Jenkins Mar 06 '20 at 18:57
  • 1
    _"hybrid pattern - Hooks + Render Props"_ These are two completely unrelated patterns and doesn't make your app hybrid in any way. – Emile Bergeron Mar 06 '20 at 18:57
  • @BillMetcalf, The problem of hooks arises from the fact that `{children}` accepted by Inner component would be anything. Further, this component Inner component has `useState` hook which I just omitted to keep the example simple. – Harshal Patil Mar 06 '20 at 19:10
  • @EmileBergeron, Just updated the question. – Harshal Patil Mar 06 '20 at 19:18
  • @Adam, passing `value` to `SortMenuItem` is not an option as it also depends upon the `select` function which is passed by `BaseDropdown` component? Also, if I move it out, it means the user of this component will have to set these values which is not desired!!! – Harshal Patil Mar 06 '20 at 19:20
  • @HarshalPatil - you don't understand the relationship between regular old functions and components. They're really similar - `render(React.cloneElement(SortMenuItem,{value}),select)` - done, now it's passed as a prop and can be moved outside. – Adam Jenkins Mar 06 '20 at 19:22
  • You're asking the wrong question. Ask how to achieve your expected end result instead of asking about your current implementation. Keep the current implementation in the question to show what you have as of now. – Emile Bergeron Mar 06 '20 at 19:23
  • 1
    @HarshalPatil - now that you've updated your question showing it's using `useState` makes it even wronger (yep, I said wronger). Because you're creating and mounting a brand new component each time the parent is rendered, it can never store it's state because it's constantly being created and mounted. – Adam Jenkins Mar 06 '20 at 19:25
  • How are you calling your `SortDropdown` component? I mean, how are you using it? And how are you passing the `SortMenuItem` to the render prop? – eMontielG Mar 06 '20 at 19:58

0 Answers0