Component Patterns: Render prop vs. hook #4486
-
As started in this week's sync: When building a new component where we need to manage internal structure while allowing for custom consumer composition, what pattern should we look to? I created this gist that has some different options, using the split panel component as the case. Some initial commentary: Option 2 is my preference in this case because the correct Outer-Inner relationship is guaranteed, unlike Option 3/4 where the consumer has to understand the intended structure for correct composition. Option 3 does not make much sense in this case because its only (potential) advantage is saving one import statement: + import {useEuiSplitPanel} from '@elastic/eui'`;
- import {EuiSplitPanelOuter, EuiSplitPanelInner} from '@elastic/eui'`; Now, my opinion would shift quite a bit on this matter if you also wanted to provide state or stateful functions:
The implied Outer-Inner relationship problem still exists, but stateful bits are better served coming from a custom hook. To Chandler's point from the sync, it's debatable whether the render prop pattern is now an anti-pattern in new hook world of React. I generally agree, but in all(?) cases where hook returns UI, we've needed to wrap the hook in an HOC for class components. |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 4 replies
-
Also, I found this post while trying to find a different one, but it makes the same point:
The last sentence is important when considering a hook that returns a component. |
Beta Was this translation helpful? Give feedback.
-
Thanks for digging into this! I think for me, this seems more like a preference for the consuming side. All the options seem pretty similar (though I wouldn't go the To me, the main thing I find hard about full hooks (option 3) is that they're less discoverable in the consumer's IDE. I think most of our devs would try to find a component by typing But this might be a great candidate to actually shop around for feedback to the different solution teams or a quick survey in our weekly newsletter. |
Beta Was this translation helpful? Give feedback.
-
Got pinged by @cjcenizal on this so I'll bring my 2 cents 😊 I am not a huge fan of hooks to return components. So not option 3. So that leaves us with the render props pattern. I wonder why we need to pass down the component like that. What about using context for this? And, if needed, expose a hook to access certain props (e.g. // context.ts
const context = React.createContext();
export const Provider = () => {
// Here comes all the different state and handler to pass down
return (
<context.Provider value={{ /* state and handlers */}}>{children}</context.Provider>
)
}
export Consumer = context.Consumer;
// Let consumers access state and handlers
export useEuiSplitPanel = React.useContext(context)); // EuiSplitPanel.ts
import { Provider, Consumer } from './context';
const Outer = () => {
return (
<Provider>
<Consumer>
{() => {
// If needed to add logic when the context change
return children;
}}
</Consumer>
</context.Provider>
)
};
const Inner = () => {
// Has access to all it needs to render through context
const context = useEuiSplitPanel();
return children;
}
export EuiSplitPanel = { Outer, Inner }; // On the consumer side it is then simply composition, no need for hooks of render props.
// The downside is that the consumer needs to wrap its code in a component
// to be able to later `useEuiSplitPanel()`. The alternative would be to have the <Consumer />
// here but then we are back to the render props
import { EuiSplitPanel } from '@elastic/eui';
export const MyComponent = () => {
return (
<EuiSplitPanel.Outer anyProp="bar" canBePassed={true}>
<EuiSplitPanel.Inner>
<MyOwnComponent />
</EuiSplitPanel.Inner>
<EuiSplitPanel.Inner>
<MyOtherComponent />
</EuiSplitPanel.Inner>
</EuiSplitPanel.Outer>
)
}; |
Beta Was this translation helpful? Give feedback.
-
Here's a draft PR with an example of using the latest suggestion without the Context support. #4539 |
Beta Was this translation helpful? Give feedback.
Got pinged by @cjcenizal on this so I'll bring my 2 cents 😊
I am not a huge fan of hooks to return components. So not option 3.
Option 4 seems a bit over-engineered to me.
So that leaves us with the render props pattern. I wonder why we need to pass down the component like that. What about using context for this? And, if needed, expose a hook to access certain props (e.g.
isPanelOpen
)?