In this article, I'll demonstrate how to implement recursion in a React component to manage radio button groups. Additionally, we'll explore a use case for the new Map syntax in JavaScript.
In this guide, we'll build a recursive radio button selector and explore why JavaScript's Map object is the ideal choice for managing its state.
Map Object?While plain objects are common, the Map object offers several advantages for dynamic state management:
map.size.We'll use a Map where the key is the nesting level (depth) and the value is the ID of the selected item at that depth.
const [choicesState, setChoicesState] = useState(new Map<number, number>());When a user selects an item at a certain level, we need to:
const handleChoices = (level: number, id: number) => {
// 1. Create a fresh Map to maintain immutability
const newChoicesState = new Map(choicesState);
// 2. Set the current level selection
newChoicesState.set(level, id);
// 3. Cleanup: Remove any "stale" selections at deeper levels
for (const [key] of newChoicesState) {
if (key > level) {
newChoicesState.delete(key);
}
}
setChoicesState(newChoicesState);
};The RadioGroup component renders a list of items. If an item is selected and it has children (sub-items), the component calls itself to render the next level.
interface Item {
id: number;
name: string;
children?: Item[];
}
const RadioGroup = ({ data, level = 0, handleChoices, choicesState }) => {
const selectedId = choicesState.get(level);
const selectedItem = data.find(item => item.id === selectedId);
return (
<div className="flex flex-col gap-4 pl-4 border-l-2 border-zinc-100">
{/* Render current level items */}
<div className="space-y-2">
{data.map((item) => (
<label key={item.id} className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name={`level-${level}`}
checked={selectedId === item.id}
onChange={() => handleChoices(level, item.id)}
className="w-4 h-4 text-primary"
/>
<span className="text-sm font-medium">{item.name}</span>
</label>
))}
</div>
{/* RECURSION: Render children if an item is selected */}
{selectedItem?.children && (
<RadioGroup
data={selectedItem.children}
level={level + 1}
handleChoices={handleChoices}
choicesState={choicesState}
/>
)}
</div>
);
};Recursive components can re-render frequently. If your tree is large, wrap your component in React.memo and use useCallback for the handleChoices function to prevent unnecessary updates.
const MemoizedRadioGroup = React.memo(RadioGroup);Recursion in React, combined with the power of JavaScript Map, provides a clean and scalable way to handle complex data structures. By clearing deeper levels on every change, you ensure your application state remains consistent and easy to reason about.
More articles you might find interesting
Learn how to streamline your API data handling by efficiently removing null, undefined, and empty values from JavaScript objects using Lodash and recursion.
Have you ever tried to log or send state immediately after calling its set function, only to find the old value instead? This is one of the most common "aha!" moments for React developers.
Discover why using array indices as keys in React can lead to performance issues and bugs, and learn the best practices for proper list rendering.