Reputation: 25803
[Note: CodeSandbox is here]
I am trying to implement a ToggleButtonGroup
which "selects" a button when clicked. Only one button can be selected at a time.
The usage should be as follows, where clicking on any of the buttons, sends an onChange
event to the parent with the button's value:
export default function App() {
const [color, setColor] = useState<string | undefined>();
return (
<div className="App">
<ToggleButtonGroup value={color} onChange={setColor}>
<ToggleButton value="R">Red</ToggleButton>
<ToggleButton value="G">Green</ToggleButton>
<ToggleButton value="B">Blue</ToggleButton>
</ToggleButtonGroup>
<h4>Color: {color}</h4>
</div>
);
}
I understand that the standard way to do this is to add properties to the child components (in this case ToggleButton
) using React.cloneElement()
. Here's my attempt to do this, but it is not working:
interface ToggleButtonProps {
value: string;
selected?: boolean;
children: ReactNode;
}
const ToggleButton = ({ selected, children }: ToggleButtonProps) => {
return <button className={selected ? "selected" : ""}>{children}</button>;
};
interface ToggleButtonGroupProps {
value: string | undefined;
children: ReactNode;
onChange: (value: string) => void;
}
const ToggleButtonGroup = ({
value,
children,
onChange
}: ToggleButtonGroupProps) => {
const buttons = React.Children.map(children, (child) => {
React.cloneElement(child, {
selected: child.props.value === value,
onClick: () => {
onChange(child.props.value);
}
});
});
return <div>{buttons}</div>;
};
The cloned ToggleButton
s are not even rendering!
ToggleButtonGroup
. How do I fix them?My CodeSandbox is here.
Upvotes: 0
Views: 644
Reputation: 616
Solution
Add onClick
to ToggleButton
component:
// ---------- ToggleButton ----------
interface ToggleButtonProps {
value: string;
selected?: boolean;
children: ReactNode;
onClick: () => void;
}
const ToggleButton = ({ selected, children, onClick }: ToggleButtonProps) => {
return <button onClick={onClick} className={selected ? "selected" : ""}>{children}</button>;
};
Alternative solution
However, I usually find that it's never worth it to mess with JSDom nodes.
For your particular case I would suggest passing props for your ButtonGroup directly and let ButtonGroup render the list.
Using Omit so I won't have to pass selected
and onClick
as they will be set by ButtonGroup.
interface ToggleButtonGroupProps {
value: string;
options: Omit<ToggleButtonProps, "selected" | "onClick">[];
onChange: (newValue: string) => void;
}
const ToggleButtonGroup: React.FC<ToggleButtonGroupProps> = (props) => (
<div className="toggle-button-group">
{props.options.map((option) => (
<ToggleButton
key={option.label}
value={option.value}
onClick={() => props.onChange(option.value)}
label={option.label}
selected={option.value === props.value}
/>
))}
</div>
);
Full example on codesandbox.io
Upvotes: 1