This context discusses the evolution of UI components in React, focusing on Headless UI components and their journey with high order components, render props, and custom hooks.
Abstract
The context begins by introducing Headless UI components, which are components without a visual representation but with UI functionality. It then delves into the history of these components, starting with high order components (HOCs), which are functions that take a component and return a new component with additional logic and behavior. The text explains how HOCs can cause unnecessary unmounting and how to avoid this issue.
The concept of render props is introduced as an alternative to HOCs, offering dynamic composition instead of static composition. Render props refer to a technique for sharing code between React components using a prop whose value is a function that returns a React element. The text provides examples of converting HOCs to render props and discusses the problem of "wrapper hell" in large-scale applications.
Headless UI components are then reintroduced as a solution to this problem, offering maximum visual flexibility by providing no interface and separating the logic and behavior of a component from its visual representation. The text explains how Headless UI components can be implemented using render props and custom hooks, which are a wrapper function surrounding existing hooks to reuse stateful logic and behavior.
The context concludes by listing several Awesome React Headless Components and rewriting a previous example using a custom hook. It emphasizes the simplicity and straightforwardness of custom hooks and encourages the construction of custom Headless UI components.
Bullet points
Headless UI components are components without a visual representation but with UI functionality.
High order components (HOCs) are functions that take a component and return a new component with additional logic and behavior.
HOCs can cause unnecessary unmounting, which can be avoided by using composition instead of mutating the original component.
Render props offer dynamic composition as an alternative to HOCs, allowing for the sharing of code between React components.
Headless UI components can be implemented using render props and custom hooks, which reuse stateful logic and behavior.
Custom hooks are a simple and straightforward way to build Headless UI components.
Several Awesome React Headless Components are listed, including React Albus, Dayzed, Downshift, React Ranger, React Selected, React T-Minus, React Toggled, React Values, and React Table.
Headless UI Components: A Journey With High Order Components, Render Props, and Custom Hooks
Headless UI components without UI but with UI functionality
We have given an introduction to React-Table — a custom Hook to build an extendable table without the actual UI component. This type of component is called a headless UI component — it separates the logic and behavior of a component from its visual representation.
Headless UI components have a long journey with high order components, render props, and custom hooks. These concepts will be explained in this article.
High Order Components
We have a component that prints out a simple UI:
For this article, the source code is verified in CodePen:
We want to make the text blue. This can be done by adding the style to line one or passing the style through line three. However, we want this style transformation logic to be reusable for other components too.
A high order component (HOC) is a good choice for this task. It’s a function that takes a component and returns a new component, with additional logic and behavior. It’s commonly defined as follows:
This HOC is named withColor (lines 1-8). It takes props and extracts out color if defined. Otherwise, it uses red as the default color (line 2). The rest props are passed down to the original component (line 5). It’s important to use composition, instead of mutating the original component.
This HOC wraps the original component by a div, styled with color (line 4), which is defined as blue at line 14.
Now we see the text blue:
You may be asking why we created TransformedComponent outside of App, instead directly mutating the original component in the render, as follows:
React’s diffing algorithm (also called reconciliation) uses component reference to determine whether it should update the existing subtree or throw it away and mount a new one. If the component returned from render is strictly equal to the component from the previous render, React recursively updates the subtree by diffing it with the new one. If they’re not equal, the previous subtree is completely unmounted.
If TransformedComponent is generated inside render, it has a new reference for each render, which would cause unnecessary unmounting.
If you have to put TransformedComponent inside render, the alternative way is to apply useMemo to keep the reference between renders.
withColor transforms the color. What if we also want to change the font style to be italic?
Another HOC is created to transform the font style:
This additional HOC is named withFontStyle (lines 1-8). It takes props and extracts out fontStyle if defined. Otherwise, it uses normal as the default fontStyle (line 2). The rest props are passed down to the original component (line 5).
This HOC wraps the original component by a div, styled with fontStyle (line 4), which is defined as italic at line 24.
Here we compose two HOCs at line 21.
What if we want to transform props in the original component, something like multiple of the original value?
Another HOC will do.
This new HOC is named withMultipleValue (lines 1 - 9). It takes props and extracts out factor if defined. Otherwise, it uses 1 as the default factor (line 2). A new value is calculated at line 3, which replaces the original value at line 6. newprops are passed down to the original component (line 8).
This HOC is applied at line 31, which times the factor, 5, defined at line 36.
We compose three HOCs in lines 31-33. These lines look a little congested but they can be formatted like this if you have ramdainstalled:
Well, HOCs use static composition, but render props use dynamic composition.
Render props refer to a technique for sharing code between React components using a prop whose value is a function that returns a React element. A component with a render prop calls this function instead of implementing its own rendering.
It is said that most HOCs can be accomplished by regular components with render props.
Conceptually, HOCs provide additional logic and behavior to existing components. Render props provide components with intended logic and behavior, which can be applied to any visual representation.
Let’s see how to convert HOC, withColor, to a render prop implementation:
Lines 1-4 define ColorComp that adopts a render prop. At line 3, the color logic is set to the outer div, which can be applied to any rendered components.
Lines 8-10 deploy ColorComp with the render prop set to BaseComponent.
Line 12 initializes color to be blue.
Although this pattern is called render props, it does not have to be named render. Any prop that is a function for rendering is technically a render prop.
We rename render to test at line 3 and 9. It also functions as it’s called render.
If you really want a HOC for some reason, you can add on to the above code by creating a HOC on top of render props.
Lines 6-8 is a HOC on top of render props (line 7). This HOC is used in line 12. It works as well as a regular HOC.
We’ve tried various ways to implement withColor using render props. How about convert withFontStyle to render props as well?
Here’s the source code:
Lines 1-4 define FontStyleComp that adopts a render prop. At line 3, the fontStyle logic is set to the outer div, which can be applied to any rendered components.
Lines 14-19 deploy FontStyleComp with the render prop set to ColorComponent that has the render prop set to BaseComponent.
Line 23 initializes fontStyle to be italic.
What about converting withMultipleValue to render props too?
Here’s the source code:
Lines 1-4 define MultipleValueComp that adopts a render prop. At line 3, it provides the logic to calculate the new value based on factor, and then renders whatever from the render prop as visual representation.
Lines 24-37 deploys FontStyleComp with the render prop set to FontStyleComponent, a nested component with render props.
Line 41 initializes factor to be 5.
We have given examples of HOCs and render props. When applications have a massive quantity of nested components, you can see the problem commonly known as “wrapper hell.”
Headless UI Components
A headless user interface component is a component that offers maximum visual flexibility by providing no interface. It separates the logic and behavior of a component from its visual representation.
A famous headless implementation is p/>. It uses children to implement render props and emphasizes the basics of Unix philosophy:
Rule of Separation: Separate policy from mechanism; separate interfaces from engines.
— Eric S. Raymond
Headless UI components with render props
Headless UI Components made render props popular since they need a way to dynamically apply the logic and behavior to any components. It delivers more flexibility for large-scale components than HOCs.
A headless UI component can offer its children methods and values:
CombinedComp at lines 1-7 creates a headless UI component that supplies its children with three methods: getFactor (line 3), getColor (line 4), and getFontStyle (line 5).
When the component is used at lines 12 -18, these methods are available to its children elements (lines 14-16). Isn’t it beautiful for logic and behavior to be reused?
Headless UI components with custom hooks
Here comes React hooks, which creates React components without extending from a class component. Besides built-in hooks, there are custom hooks, which are a wrapper function surrounding existing hooks. It’s a mechanism to reuse stateful logic and behavior that are isolated for each usage.
Hooks are a tornado in React world. They deliver the promise of functional programming. Hooks are easy to compose and straightforward to use. They end the “wrapper hell” caused by render props.
However, headless UI components continue. With the assistance of custom hooks, headless UI components have become more popular.