React is a powerful and popular JavaScript framework for frontend development. Every framework has its way, style, and syntax of writing code; React is no different.
In this article, we will see advanced React component patterns that frontend developers who use React should know. Patterns are a way to fix the recurring problems. Following the patterns is a standard programming practice that helps to create and maintain a standard that enforces rules and helps to maintain the code quality and improve the developer experience in an enterprise codebase.
Renderless or logic, or Headless components are the components that abstract the logic, state management from the view or presentation, which means they do not render any view themselves; rather, they expose the state, methods to the children, and the children can use them as they wish.
Separating the logic and the view gives the flexibility on how you want to structure and style the UI.
Here, the HeadlessModal component will encapsulate the state and the logic, and give the control to the child to render the UI however they wish.
// HeadlessModal.js
import { useState, useEffect } from "react";
const HeadlessModal = ({
children,
isOpen: controlledIsOpen,
onOpenChange,
}) => {
const [internalIsOpen, setInternalIsOpen] = useState(false);
// Handle controlled or uncontrolled state
const isOpen =
controlledIsOpen !== undefined ? controlledIsOpen : internalIsOpen;
const setIsOpen =
controlledIsOpen !== undefined ? onOpenChange : setInternalIsOpen;
const open = () => setIsOpen?.(true);
const close = () => setIsOpen?.(false);
const toggle = () => setIsOpen?.(!isOpen);
// Handle ESC key
// Hides the modal on ESC
useEffect(() => {
const handleEsc = (event) => {
if (event.key === "Escape" && isOpen) {
close();
}
};
if (isOpen) {
document.addEventListener("keydown", handleEsc);
document.body.style.overflow = "hidden"; // Prevent scroll when modal is mounted
}
return () => {
document.removeEventListener("keydown", handleEsc);
document.body.style.overflow = "unset";
};
}, [isOpen]);
return children({
isOpen,
open,
close,
toggle,
});
};
const HeadlessModalExample = () => {
return (
<div>
<HeadlessModal>
{({ isOpen, open, close }) => (
<>
<button onClick={open}>Open Modal</button>
{isOpen ? (
<div
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0,0,0,0.5)",
display: "flex",
alignItems: "center",
justifyContent: "center",
zIndex: 1000,
}}
>
<div
style={{
backgroundColor: "white",
padding: "20px",
borderRadius: "8px",
maxWidth: "500px",
width: "90%",
}}
>
<h2>Modal Title</h2>
<p>This is modal content!</p>
<button onClick={close}>Close</button>
</div>
</div>
) : null}
</>
)}
</HeadlessModal>
</div>
);
};
export { HeadlessModal, HeadlessModalExample };
As the name suggests, polymorphic components are the components that can be rendered as different HTML elements or React components while maintaining the same behavior and using the same API.
This is achieved through a prop (generally as), which determines using what element this component should render.