#How exactly does a component returned from a useForm hook not fall into an anti-pattern?

23 messages · Page 1 of 1 (latest)

lyric spear
#

For the past few days, I have been struggling with creating a hook that returns a component because I really needed a way to link a hook and a component without using global state or context.

What I found in the React docs is that defining a component inside another component is considered an anti-pattern because on every render it will not be considered the same component.

My first thought was basically to put it inside useMemo, but while that may help with re-renders, from what I understand the virtual DOM will still treat it as a different type.

I noticed that useForm has code like this inside a useMemo:

extendedApi.Field = function APIField(props) {
  return <Field {...props} form={formApi} />
}```

So I am wondering if anyone has knowledge, documentation links, or references to React sources where I can learn exactly why and how defining a component inside another component is considered an anti-pattern. I would really, really appreciate it because I’m going crazy trying to find a good answer and haven’t had any luck so far.
sweet palm
# lyric spear For the past few days, I have been struggling with creating a hook that returns ...

The anti-pattern in question here is that you're merging two concerns that React tries to separate (logic vs. rendering)

Hooks are for logic, components are for rendering. Mixing the two can cause issues when you're not confirmed to always use the component.

You might be familiar with he pattern of

const foo = useFoo()

return <Bar {...foo.getBarProps()} />

This makes it easier to adjust Bar because it simply needs to adhere to the created props from the hook.

However, I think there's good reasons to use them sometimes. For example, I don't like redundant patterns like:

const foo = useFoo()

return (
  <FooProvider foo={foo}>
     {renderFoo(foo, { props: foo.getFooProps() })}
  </FooProvider>
)

// If we simply forwarded the component or premade it
return <foo.Provider>
  <foo.Render />
</foo.Provider>
#

also, compound components are very common and not an anti-pattern. As far as I'm concerned, there's little difference between them

<Dropdown>
  <Dropdown.Toggle>Select</Dropdown.Toggle>
  <Dropdown.Menu>
   <Dropdown.Item>Item</Dropdown.Item>
   <Dropdown.Item.Text>Not clickable</Dropdown.Item.Text>
  </Dropdown.Menu>
</Dropdown>
#

from searching a bit, I'm not sure where they explicitly say it's an antipattern. You said you found it in the react docs, but you wished for a link to the React docs that explains it. Perhaps the AI simply pretended that React calls it an anti-pattern?

There's tons of documentation that documents hooks and their intended use being stateful behaviour, so it's not a stretch to say that mixing rendering into a hook is not something React encourages

#

As far as "component inside another component" goes, that is much more explicitly an anti-pattern. Since functions are hoisted in JavaScript, there's basically no reason to nest components because everything could've just been passed as prop from the start.

lyric spear
# sweet palm The anti-pattern in question here is that you're merging two concerns that React...

Thank you for the detailed explanation. What i basically meant by the anti-pattern is defining a component inside another component. The useForm hook seems to do this by providing a Field component from the useForm hook (at least from my understanding).

It is wrapped in useMemo, which helps with rerenders, but from my understanding it could still cause issues with the Virtual DOM? The React docs you provided are exactly the ones I was referring to, they basically say that this pattern can be very slow and can cause bugs. However, I cant find information explaining what the exact underlying issue is (other than reddit post i provided). Is this just rerender? Then it infact can be fixed with useMemo, or is it about react seeing a different type on each rerender.

I'm trying to improve and deepen my understanding of React, specifically around re-rendering. I am reading this article…

sweet palm
sweet palm
#

all the examples listed are because there's a new component at every render, instead of a memoized component causing issues.

That's still an anti-pattern because it's easy to forget about the memoization and end up with that issue, even if you didn't want to

#

It's the same anti-pattern as conditionally calling hooks. You technically can if you know what you're doing, but it's easy to break and you're bound to run into issues without triple checking

lyric spear
# sweet palm It's the same anti-pattern as conditionally calling hooks. You technically can i...

What bothers me is the last sentence from one of the comments:

Also… if you don't want the extra renders, even at the VDOM level, you can memoize the child component. But if you define the component in the parent, you get a new component every render, so the memoization doesn't achieve anything.
This also kind of makes sense, because if the parent re-renders where the hook is used it will still consider the Field component a new type, since useMemo will be re-run with a new copy.
so until parent rerenders useMemo is the solution but parents will probably rerender often

#

Also i really appreciate that you are trying to help me, even though this isnt technically a useForm-related issue.

sweet palm
# lyric spear What bothers me is the last sentence from one of the comments: > Also… if you do...

The sentence is correct. If you define the component in the parent, you get a new component every render.

They mean this code:

function Parent() {
  const Component = () => {
    const [count] = useState(0)
    return <div>{count}</div>
  }

  return <Component />
}

The "memoize" in this case probably refers to memo(MyComponent) rather than useMemo. Which I'm not sure you can do with the hook component pattern, so I suppose that is a point towards defining components outside.

Either way, that would have to be done by us even before assigning it, and in this case React.memo doesn't really make sense. Again, it's probably good to avoid this on principle because this takes valuable time off of your actual task to figure out and research.

#

^ this pitfall is rather easy to detect by the way. If you had an input in any component below the one in question, it would "lose focus" or lose state everytime you tried to type in it.

#

because React sees it as new component, unmounts the old one and remounts the new one

lyric spear
#

To be honest, I didnt even have a problem with my hook since I used useMemo from beginning. It was just the Reddit comments that made me think about my code.

#

I doubt that even if it is always a new type in virtual dom it will really affect performance wise this much

sweet palm
#

I was worried we missed something crucial related to React, but I think I understood what the comments were discussing

#

and I definitely get why it's considered an anti-pattern, because if you need to explain the VDOM behaviour of React in order to write your component, then you're probably not writing it the way React recommends

lyric spear
#

i didnt want to use context, or global state or passing props from hook to component which all are the only way to go so i decided to link my hook to a component by defining a component inside the hook

sweet palm
#

I believe we only used it a single time at our workplace, that being a Layout component that renders two invisible divs for measurements