This content originally appeared on Bits and Pieces - Medium and was authored by Thor Chen
A Potential New Pattern Inspired by “Partial Application”: With practical examples using Material-UI and TypeScript
Hooks have been introduced to the React community many years, and there are many new coding patterns emerged because of that. In this article, I would like to share my journey of exploring a potential pattern inspired by the concept of “partial application” — I call it “Return Component From Hooks”.
Let’s start with a simple example:
- Add a button to trigger the popup menu
- Manage the states to show/hide menu
- Handle clicks of menu items
It looks fine when there is only one place where we need popup menu. However, we usually have many menus in many places. To achieve that, we may need to duplicate the boilerplate code across all these places — that’s not fun.
First iteration: Share logic with hooks
It should be straightforward to identify and extract the common logic into a custom hook, let’s call it useMenu:
This hook encapsulates the most outstanding common parts we would duplicate:
- The props we’d like to pass to a <Menu /> component: menuProps (anchorEl, open, onClose)
- The callbacks we’d like to pass to the interactive elements that handle clicks: openMenu, closeMenu
Based on that, we can rewrite the example to a new version:
It is worth noting that we are spreading common props returned from hooks to Menu component:
<Menu {...menuProps}>
This pattern is already used by many hook-based libraries such as react-table and react-hook-form.
This is already looking good, right? But it feels like we could still explore further: how about we return a <Menu /> component from useMenu hook that already has menuProps bound?
Second iteration: Return component from hooks
The idea of returning component with bound props from hook is actually coming from a pattern in functional programming paradigm: partial application.
Imagine that we have an function called add that can add 3 numbers:
const add = (x, y, z) => x + y + z;
add(1, 2, 3); // x=1,y=2,z=3
We can then convert it in a way that x can be bound first to generate a new function that allows us to supply y and z later:
const addX = (x) => (y, z) => x + y + z;
const addOne = addX(1); // bound x=1
addOne(2, 3); // x=1,y=2,z=3
addOne(3, 4); // x=1,y=3,z=4
While props of React components are not really function arguments, but the pattern of thinking is the same — let’s try binding the common part of props!
A naive implementation is to create a wrapped <Menu /> component inside useMenu, and the new <Menu /> component has common props dynamically bound to the values we previously put in menuProps(anchorEl, open, onClose):
So that in the new version of the example, we don’t need to use and spread the menuProps anymore:
Logically, it works.
But wait a sec, if you run the code, you may spot that the fade-out animation is gone when menu closing. What happened?
To figure it out, let’s replace the usage of fundamental Menu component from MUI with our wrapped version (just to add some logs):
So when replacing the import statement from @mui/material/Menu to ./Menu, we can see the logs for mount and unmount while all features remain the same.
But we see that every time the menu open and close, console log says the Menu component is unmounted and remounted! Why?
In the React official docs, it says:
Whenever the root elements have different types, React will tear down the old tree and build the new tree from scratch.
That is, when returning the Menu component from the useMemo hook, we are returning a new component every time when dependency array changes, and the type is not the same as the previous one, so React is going to build a new component tree from there.
While this works, it’s not perfect — I would want to return component with bound props from hooks without this caveat.
Third iteration: Return statically defined component from hooks
Is it possible that we return statically defined component from the hook while keeping props bound? Challenge accepted.
If we are rethinking how we could pass dynamic values, props is not the only choice — we also have Context. What if we define a wrapped component that is taking values we want to bind from Context instead of props?
Let’s define the Context and Provider first:
Then we can create the wrapped component using MenuContext and return it in our useMenu hook:
According to this implementation:
- The Menu component returned from the useMenu hook is statically defined
- The menuProps to be bound is managed in MenuContext
- The MuiMenu component under the hood does have the props partially bound to menuProps dynamically
Thus, when using this version of useMenu hook, we no longer need to pass the menuProps manually, and the unmount-remount behaviour won’t happen because the type for Menu component in the component tree won’t change when bound props change:
However, because we are relying on Context, we should not forget to put the Provider in a higher-level of the component tree:
Note: because we usually will not have more than one popup menus opening at the same time, a single MenuContextProvider in the near-top level should be enough. If we do want to have a different Menu binds to a different MenuContext, then we need to use a separate MenuContextProvider.
Demo
Conclusion
As seen in the code examples, using hooks to encapsulate shared logic is a good start, but we can keep exploring more.
Inspired by the concept of “partial application”, we can try to return a component with props that have been partially bound from a custom hook.
However, the naive implementation will return new component definition when bound props changed, and that leads to unmount-remount behaviour that we may not expect.
Luckily, the challenge of returning statically defined component with dynamically bound props can be solved by introducing Context, and we made an example with running code.
Related articles
- Functional Programming: Currying vs. Partial Application
- New React Hooks Pattern? Return a Component
- How do you feel about a hook returning components?
Build component-driven. It’s better, faster, and more scalable.
Forget about monolithic apps, start building component-driven software. Build better software from independent components and compose them into infinite features and apps.
OSS Tools like Bit offer a great developer experience for building component-driven. Start small and scale with many apps, design systems or even Micro Frontends. Give it a try →
Learn more
- Building a React Component Library — The Right Way
- 7 Tools for Faster Frontend Development in 2022
- Microservices are Dead — Long Live Miniservices
React Design Patterns: Return Component From Hooks was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Bits and Pieces - Medium and was authored by Thor Chen
Thor Chen | Sciencx (2022-02-17T08:02:26+00:00) React Design Patterns: Return Component From Hooks. Retrieved from https://www.scien.cx/2022/02/17/react-design-patterns-return-component-from-hooks/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.