Design Challenge 002: Simplifying State Management in React
Thinking of Sharing Logic Across Multiple UIs
Welcome to another issue of The Pragmatic Developer! In this issue, we explore an interesting challenge I encountered during a recent bug-fixing task. This scenario is a perfect example to demonstrate the Headless Component pattern. Let's dive into the background and design challenge, and I encourage you to think about how you would implement it.
The Approval Panel Component
There is an Approval Panel component on our product page that allows a user to either approve or reject an approval request. This is useful in typical workflows. For example, an employee onboarding request might include an action like buy a new laptop for the employee, which needs a manager's approval before moving to the next stage (so the downstream system can arrange the purchase and delivery, etc.).
The user interface looks like this:
There are two buttons in the panel, allowing the person with permission to decide whether to continue. When the user clicks Approve
, the current request moves to the next phase (pending
, for example). When Decline
is clicked, it flows back to the previous step (rejected
, for example). Each button triggers an async call to the backend service /rest/approval/<id>/approve or /rest/approval/<id>/reject accordingly.
Of course, you'll need to consider error handling and loading states when the request is made. For now, let's keep it simple and not handle the retry
case.
A quick prototype of the implementation would look like this:
const ApprovalPanel = ({id}) => {
const [isDone, setDone] = useState(false);
const handleApprove = () => {
fetch('POST', `/rest/approval/${id}/approve`)
.then(r => r.json())
.then(data => setDone(data.isDone));
}
// handleDecline...
// Only show the component when the approval isn't completed
if (isDone) {
return null;
}
return (
<div>
<h2>This request requires your approval</h2>
<Button onClick={handleApprove}>Approve</Button>
<Button onClick={handleDecline}>Decline</Button>
</div>
);
}
New Variation
The code works well for a while, but now there is another place that needs to show similar controls allowing the user to either Approve
or Decline
, but with a different UI. The buttons' layout is different; instead of horizontal, they are vertical, and the button colors are also different.
If you were to implement the new ApprovalPanel
component, how would you structure your code to make it both extensible and flexible? Or consider a different use case where, in a context menu, we want to show the Approve
and Decline
options along with other operations.
Think about how you can use the Headless Component pattern to achieve this flexibility and share your thoughts. We'll discuss my solution in the next issue.
I hope you found this design challenge intriguing. Try implementing the solution yourself and think about how the Headless Component pattern can make your code more flexible and extensible. In the next issue, I’ll share my approach to this problem and discuss how you can use this pattern in your projects.
As always, feel free to share your thoughts and solutions. Happy coding!