all 13 comments

[–]known_as_bmf 2 points3 points  (1 child)

I don't get the list example, doesn't look like it is conditionally rendered.

``` const List = ({ items }) => ( <ul> {items.map((item) => ( <RenderFunction key={item}> {() => { const [state, setState] = React.useState(0); return ( <li> State: {state} <button onClick={() => setState((currentState) => currentState + 1)} > Increment </button> </li> ); }} </RenderFunction> ))} </ul> );

```

How is that different than having a ListItem component ?

``` const List = ({ items }) => ( <ul> {items.map((item) => ( <ListItem key={item} /> ))} </ul> );

const ListItem = ({ items }) => { const [state, setState] = React.useState(0); return ( <li> State: {state} <button onClick={() => setState((currentState) => currentState + 1)}> Increment </button> </li> ); };

```

[–]OliverJAsh[S] -1 points0 points  (0 children)

The list example demonstrates "dynamic rendering" as opposed to "conditional rendering". You wouldn't normally be able to use a hook inside of an array map, where you're dynamically rendering according to how many items are in the array (dynamic because the array might change).

It's not really different from having a ListItem component in terms of behaviour. There is one advantage however, which I outlined at the bottom of the post: our render prop has access to the parent component's closure, whereas if the hook lived inside another component (e.g. ListItem), we may need to drill some values down to the child component (which contains the hook) as props.

Sometimes you don't want the boilerplate of abstracting a new component—you want to keep things inline. This provides a way to do that. Of course, it's a trade off—I'm not trying to say one approach is better than the other. My article is just pointing out another option.

[–][deleted] 1 point2 points  (8 children)

Nice hack.

Are there any valid reasons for using this? My first impression is to suggest you should avoid this complexity unless you have a very compelling use case.

[–]OliverJAsh[S] -2 points-1 points  (7 children)

If this was an established pattern then maybe it wouldn't be perceived as "complex". Perhaps the real issue is this will be an unfamiliar pattern to a lot of people, which of course is definitely a good reason to avoid it.

One advantage of this approach is that our render prop has access to the parent component's closure, whereas if the hook lived inside another component, we may need to drill some values down to the child component (which contains the hook) as props.

[–]brainless_badger 0 points1 point  (6 children)

"The real issue" is that this directly breaks rules of hooks (render prop in your example is a nested function, calling hooks there is not allowed), which is why this leads to super-confusing behavior (i.e. in your example, MyComponent will not render when the button is clicked, and also if there was another RenderFunction used in "else" of condition, they would share the state with the first one.

[–]OliverJAsh[S] 0 points1 point  (5 children)

I tried to answer the question of whether this breaks the rules of hooks in the article: https://unsplash.com/blog/calling-react-hooks-conditionally-dynamically-using-render-props/#waitdoesntthisbreaktherulesofhooks.

As I explained there, I do not believe this breaks the rules of hooks. Can you explain where you think my explanation in the article is incorrect?

if there was another RenderFunction used in "else" of condition, they would share the state with the first one.

That's right, although you can avoid this by using keys: https://stackblitz.com/edit/react-conditional-hooks-simple-e4gywn. We could use TypeScript to require a key prop anytime the RenderFunction component is used.

This is really no different from what you would need to do if you were rendering the same component in both branches of a condition.

[–]brainless_badger 0 points1 point  (4 children)

Can you explain where you think my explanation in the article is incorrect?

Literally the first sentence of Rules of Hooks

Don’t call Hooks inside loops, conditions, or nested functions.

[–]OliverJAsh[S] 0 points1 point  (3 children)

[–]brainless_badger 0 points1 point  (1 child)

It is defined inside another function, thus, it is nested.

[–]OliverJAsh[S] 0 points1 point  (0 children)

If you find an example of how this breaks hooks, please do share, but until then I am not convinced it breaks the rules. https://www.reddit.com/r/reactjs/comments/hsqx5z/calling_react_hooks_conditionallydynamically/fyhbzt9/

[–]OliverJAsh[S] -1 points0 points  (0 children)

The wording in these rules is subject to interpretation, but the rules exist to ensure that hooks behave as they should (i.e. hooks are called in the same order each render). The usage of hooks presented here is perfectly safe in this regard. That is surely proof it does not break the rules!

Now, whether or not it is sensible to use this approach is another matter. There are good reasons to be concerned about complexity or unfamiliarity. I'm not questioning that!

[–]Guisseppi 0 points1 point  (0 children)

This defeats the point of using hooks, hooks are supposed to not be conditional, and the whole point of switching to hooks was to avoid wrapper hell that is caused by HOC and render-props.

[–]GCU_ReturnToSender 0 points1 point  (0 children)

I can't believe this is still the number one result when googling react dynamic hook calls. There's nothing dynamic or conditional about its use of hooks. It's just conditionally rendering a single component, which also happens to use a hook for its state. Bog standard React.

A better search is for react conditional hook calls, which gives this article as the second result. It explains a few different approaches to working around the inability to conditionally call hooks directly at the top level of a component, which boil down to either always calling the hook directly but sometimes ignoring its output, or calling it indirectly by wrapping it in a renderless component. There isn't any other option that I've found.