Reputation: 1297
I have 2 almost identical components that map through an array of options and generate either checkboxes or radio buttons. The only difference between the components is the child component that is rendered (either a checkbox or a radio input).
I would like to reduce the duplicate code in these components but I'm unsure as to the best way to tackle it. I could consolidate the 2 components into a single component such as FormControlGroup
and add more props to allow me to select if I want either checkboxes or radio buttons to be rendered e.g. checkboxOrRadio
, but this would mean that if I added new variations of checkboxes or radio buttons such as CustomCheckbox
I would have to keep expanding on the props of this component.
<FormControlGroup
label="Radio buttons"
name="test"
options=[{label: 'one', value: 1}, {label: 'two', value: 2}, {label: 'three', value: 3}]
checkboxOrRadio="radio"
/>
Is it possible (and sensible) to pass a component in as a prop and have the component rendered in the map function? If so, how would I do this? Or are there any more elegant solutions? That way I could pass in any component I want and it will be rendered and have the key, name, label, onChange, value, checked
props passed to it.
CheckboxGroup.js
import React from 'react';
import CheckboxInput from './CheckboxInput';
const CheckboxGroup = ({ label, name, options, onChange, children }) => {
return (
<div className="form-group form-inline">
<span className="faux-label">{label}</span>
{children}
<div className="form-inline__field-container">
{options &&
options.map(option => (
<CheckboxInput
key={option.value}
name={name}
label={option.label}
onChange={onChange}
value={option.value}
checked={option.value}
/>
))
}
</div>
</div>
);
};
export default CheckboxGroup;
RadioGroup.js
import React from 'react';
import RadioInput from './RadioInput';
const RadioGroup = ({ label, name, options, onChange, children }) => {
return (
<div className="form-group form-inline">
<span className="faux-label">{label}</span>
{children}
<div className="form-inline__field-container">
{options &&
options.map(option => (
<RadioInput
key={option.value}
name={name}
label={option.label}
onChange={onChange}
value={option.value}
checked={option.value}
/>
))
}
</div>
</div>
);
};
export default RadioGroup;
Upvotes: 1
Views: 2236
Reputation: 2645
It is absolutely sensible in certain cases to pass a component as a prop. In fact, it is a pattern used in many React libraries, including React Router, which allows you to pass a component
prop to the Route
component.
In your case, the render
function of your FormControlGroup
component might look something like this:
render({component, ...}) { // component is a prop
const InputComponent = component;
return (
... // outer divs
{ options.map(option =>
<InputComponent key={option.value} ... />
}
...
)
}
Then you would use it like this:
<FormControlGroup
label="Radio buttons"
name="test"
options=[{label: 'one', value: 1}, {label: 'two', value: 2}, {label: 'three', value: 3}]
component={CheckboxInput}
/>
Another option would be to create a new component that takes care of rendering the outer <div>
elements and then map options
to a list of input components in some outer component. That allows you to make fewer assumptions about what props any given input component should be expecting. Since you're already using children
, you'd have to split it into two components. Here's one possibility for what that might look like:
const FormControlGroup = ({ label, children }) => {
return (
<div className="form-group form-inline">
<span className="faux-label">{label}</span>
{children}
</div>
);
};
const FormControlOptions = ({ children }) => {
return (
<div className="form-inline__field-container">
{children}
</div>
);
};
const SomeOuterComponent = ({ label, name, options, onChange }) => {
<FormControlGroup label={label}>
... // other children
<FormControlOptions>
{
options.map(option =>
<CheckboxInput
key={option.value}
name={name}
label={option.label}
onChange={onChange}
value={option.value}
checked={option.value}
/>
)
}
</FormControlOptions>
</FormControlGroup>
}
Of course, there are many ways to design this. The exact implementation you go with will depend on your use case.
Upvotes: 2
Reputation: 201
I personally wouldn't change it into a FormControlGroup component because it makes it simpler in the code where you're using the component. So you can quickly know what is a CheckboxGroup vs RadioGroup.
Changing it would mean checking the checkboxOrRadio prop every time to see exactly what component you were changing.
I'd say wait, if you start to get more elements you want the exact same way then create a common component. But for just two components to share some code it isn't a big deal.
Upvotes: 0
Reputation: 106
Doesn't seem like a bad idea to me. It's definitely possible, maybe it's not the most elegant solution, but I don't currently see what's wrong with it.
Upvotes: 0