I have created two separate components and a parent component. I am trying to see how I can connect them so that I can have the dropdown for the children vanish when their checkbox is unchecked. I think I may have created this so the 2 components can't communicate, but I wanted to see if there was a way to get them to. Been trying different ways, but cannot seem to figure it out.
This is the parent component. It builds sections from some data and renders a checkbox treeview with the first (parent) checkbox having a dropdown. When the third option is selected in this dropdown, it renders in a dropdown for each child checkbox. I am trying to see if I can have the child dropdowns vanish when the checkbox is unchecked, but I can't seem to get the 2 components to communicate.
export default class CheckboxGroup extends PureComponent {
static propTypes = {
data: PropTypes.any.isRequired,
onChange: PropTypes.func.isRequired,
counter: PropTypes.number,
};
mapParents = (counter, child) => (
<li key={child.get('name')} className='field'>
<SegmentHeader style={segmentStyle} title={child.get('label')} icon={child.get('icon')}>
<div className='fields' style={zeroMargin}>
<div className='four wide field'>
<TreeCheckbox
label={`Grant ${child.get('label')} Permissions`}
counter={counter}
onChange={this.props.onChange}
/>
{child.get('items') && this.buildTree(child.get('items'), counter + child.get('name'))}
</div>
<div className='twelve wide field'>
<GrantDropdown label={child.get('label')} childItems={child.get('items')}/>
</div>
</div>
</SegmentHeader>
</li>
)
mapDataArr = (counter) => (child) => (
(counter === 0 || counter === 1000) ?
this.mapParents(counter, child)
:
<li key={child.get('name')}>
<TreeCheckbox label={child.get('label')} onChange={this.props.onChange}/>
{child.get('items') && this.buildTree(child.get('items'), counter + child.get('name'))}
</li>
)
buildTree = (dataArr, counter) => (
<ul key={counter} style={listStyle}>
{dataArr.map(this.mapDataArr(counter))}
</ul>
)
render() {
return (
<div className='tree-view'>
{this.buildTree(this.props.data, this.props.counter)}
</div>
);
}
}
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
const pointer = { cursor: 'pointer' };
class TreeCheckbox extends PureComponent {
static propTypes = {
onChange: PropTypes.func,
label: PropTypes.string,
currentPerson: PropTypes.any,
};
componentDidMount() {
if (this.props.currentPerson.get('permissions').includes(this.props.label)) {
this.checkInput.checked = true;
this.changeInput(this.checkInput);
}
}
getLiParents = (el, parentSelector) => {
if (!parentSelector) parentSelector = document; // eslint-disable-line
const parents = [];
let parent = el.parentNode;
let o;
while (parent !== parentSelector) {
o = parent;
if (parent.tagName === 'LI') parents.push(o);
parent = o.parentNode;
}
return parents;
}
traverseDOMUpwards = (startingEl, steps) => {
let elem = startingEl;
for (let i = 0; i < steps; i++) {
elem = elem.parentNode;
}
return elem;
}
markIt = (nodeElem, checkIt, indeter) => {
const node = nodeElem;
const up = this.traverseDOMUpwards(node, 1);
node.checked = checkIt;
node.indeterminate = indeter;
this.props.onChange(up.children[1].innerText, checkIt);
}
changeInput = (event) => {
const e = event === this.checkInput ? event : event.target;
const selector = 'input[type="checkbox"]';
const querySelector = (el) => el.querySelectorAll(selector);
const container = this.traverseDOMUpwards(e, 2);
const markAllChildren = querySelector(container.parentNode);
const checked = e.tagName === 'LABEL' ? !markAllChildren[0].checked : e.checked;
const siblingsCheck = (element) => {
let onesNotRight = false;
const sibling = [].slice.call(element.parentNode.children);
sibling.filter(child => child !== element).forEach(elem => {
if (querySelector(elem)[0].checked !== querySelector(element)[0].checked) {
onesNotRight = true;
}
});
return !onesNotRight;
};
const checkRelatives = (ele) => {
let el = ele;
if (el.tagName === 'DIV') el = el.parentNode;
if (el.tagName !== 'LI') return;
const parentContainer = this.traverseDOMUpwards(el, 2);
if (siblingsCheck(el) && checked) {
this.markIt(querySelector(parentContainer)[0], true, false);
checkRelatives(parentContainer);
} else if (siblingsCheck(el) && !checked) {
const parent = this.traverseDOMUpwards(el, 2);
const indeter = parent.querySelectorAll(`${selector}:checked`).length > 0;
this.markIt(querySelector(parent)[0], false, indeter);
checkRelatives(parent);
} else {
for (const child of this.getLiParents(el)) {
this.markIt(querySelector(child)[0], false, true);
}
}
};
for (const children of markAllChildren) {
this.markIt(children, checked, false);
}
checkRelatives(container);
};
getRef = (input) => { this.checkInput = input; }
render() {
const { label } = this.props;
return (
<div className='permission-item'>
<div className='ui checkbox'>
<input type='checkbox' onChange={this.changeInput} ref={this.getRef}/>
<label onClick={this.changeInput} style={pointer}>
{label}
</label>
</div>
</div>
);
}
}
const mapStatetoProps = (state) => ({
currentPerson: state.get('currentPerson'),
});
export default connect(mapStatetoProps)(TreeCheckbox);
class GrantDropdown extends AbstractSettingsComponent {
static propTypes = {
label: PropTypes.string,
currentPerson: PropTypes.any,
counter: PropTypes.number,
permissionOptions: PropTypes.any,
};
state = {
items: new List(),
}
componentDidMount() {
if (this.props.childItems) {
this.getAllChildLabels(this.props.childItems);
}
}
getAllChildLabels = (childItems) => {
let list = new List();
for (const item of childItems) {
list = list.push(item.get('label'));
if (item.get('items')) {
for (const childItem of item.get('items')) {
list = list.push(childItem.get('label'));
}
}
}
this.setState({ items: list });
}
handlePermissionChange = (label) => (e, { value }) => {
this.updatePerson(['locationsPermissionsMap', label], value);
}
mapItems = (val, i) => { // eslint-disable-line
const locationVal = this.props.currentPerson.getIn(['locationsPermissionsMap', val]);
return (
<div className={locationVal === 2 ? 'two fields' : 'field'} style={zeroMarginBottom} key={i}>
<OptionSelector
options={this.firstThreePermissionOpt()}
defaultValue={locationVal || 0}
onChange={this.handlePermissionChange(val)}
/>
{locationVal === 2 &&
<div className='field' style={zeroMarginBottom}>
<LocationMultiSelect name={val} {...this.props}/>
</div>
}
</div>
);
}
render() {
const { label, currentPerson } = this.props;
if (!currentPerson.get('permissions').includes(label)) {
return null;
}
const locationLabel = currentPerson.getIn(['locationsPermissionsMap', label]);
return (
<div className={ locationLabel === 2 ? 'two fields' : 'field'} style={zeroMarginBottom}>
<div className='field'>
<OptionSelector
options={this.getPermissionOptions()}
defaultValue={currentPerson.getIn(['locationsPermissionsMap', label]) || 0}
onChange={this.handlePermissionChange(label)}
/>
{locationLabel === 3 && this.state.items.map(this.mapItems)}
</div>
{locationLabel === 2 &&
<div className='field'>
<LocationMultiSelect name={label} {...this.props}/>
</div>
}
</div>
);
}
}
const mapStatetoProps = (state) => ({
currentPerson: state.get('currentPerson'),
locations: state.get('locations'),
});
export default connect(mapStatetoProps)(GrantDropdown);
What you can do is set couple of props to send to child component to re-render them.
Example
export default class CheckBoxComponent extends React.Component {
changeInput() {
this.props.onCheckedChanged();
}
render() {
return(
<div className='permission-item'>
<div className='ui checkbox'>
<input type='checkbox' onChange={this.changeInput} ref={this.getRef}/>
<label onClick={this.changeInput.bind(this)} style={pointer}>
{label}
</label>
</div>
</div>
)
}
}
export default class DropDownComponent extends React.Component {
renderSelect() {
// here render your select and options
}
render() {
return(
<div>
{this.props.checkboxChecked === false ? this.renderSelect : null}
</div>
)
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
checkboxChecked: false
};
}
onCheckedChanged() {
this.setState({ checkboxChecked: !this.state.checkboxChecked });
}
render() {
return(
<div>
<CheckBoxComponent onCheckedChanged={this.onCheckedChanged.bind(this)} />
<DropDownComponent checkboxChecked={this.state.checkboxChecked} />
</div>
)
}
}
You can set the <input/> name attribute into a corresponding property in your state so the handler can get the name of the list / input via the event parameter and set the state respectively.
Then you can conditionally render the Dropdown according to the state.
Here is a small example of such behavior:
const lists = [
[
{ value: "0", text: "im 0" },
{ value: "1", text: "im 1" },
{ value: "2", text: "im 2" }
],
[
{ value: "a", text: "im a" },
{ value: "b", text: "im b" },
{ value: "c", text: "im c" }
]
];
const DropDown = ({ options }) => {
return (
<select>
{options.map(opt => <option value={opt.value}>{opt.text}</option>)}
</select>
);
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
showList0: false,
showList1: true
};
this.toggleCheck = this.toggleCheck.bind(this);
}
toggleCheck(e) {
const listName = e.target.name;
this.setState({ [listName]: !this.state[listName] });
}
render() {
return (
<div>
{lists.map((o, i) => {
const listName = `showList${i}`;
const shouldShow = this.state[listName];
return (
<div>
<input
type="checkbox"
name={listName}
checked={shouldShow}
onChange={this.toggleCheck}
/>
{shouldShow && <DropDown options={o} />}
</div>
);
})}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Related
I have been seeing solutions in other questions, such as placing anonymous function to the onClick, removing things from the render, but I keep getting the same problem, the file that marks me where the problem is is the ProductCard.tsx, which has this:
interface PastRouteinfo {
route: string
paymentMethod?: string
}
interface IProps {
settings?: Settings
product: Product
onProductClick?: (product: Product) => void
onGoToCart?: () => void
tracking?: any
cartModel: CartModel
hidePrice?:boolean
history:any
pastRouteInfo?: PastRouteinfo
}
interface IState {
isOpen: boolean
showToast: boolean
nameToRender: any
effectivePrice: any
offerPrice: any
}
class ProductCard extends React.Component<IProps, IState> {
state: IState = {
isOpen: false,
showToast: false,
nameToRender: '',
effectivePrice: 0,
offerPrice: 0
}
componentDidMount() {
const { product } = this.props
const { name } = product
const nameToRender = this.isLongName(name) ? name.slice(0,25) + '...' : name
const effectivePrice = product?.showPrice.price
const offerPrice = product && product.showPrice.offerPrice !== 0 &&
product.showPrice.offerPrice
this.setState({
nameToRender,
effectivePrice,
offerPrice
})
}
componentWillUnmount() {
// fix Warning: Can't perform a React state update on an unmounted component
this.setState = (state,callback)=>{
return;
};
}
onProductClick = () => {
const { onProductClick, product, history, pastRouteInfo = null } = this.props
if(pastRouteInfo !== null) {
this.setState({ isOpen: false })
const data = {
fromSuggested: true,
product,
pastRouteInfo,
}
const routeInfo = `/vendor/${product.providerId}/product/${product._id}`
history.push(routeInfo, data)
return
}
if (!onProductClick && pastRouteInfo === null) {
this.setState({
isOpen: true,
})
} else if(onProductClick) {
onProductClick(product)
}
}
dismissModal = () => {
setTimeout(() => {
this.setState({
isOpen: false,
})
}, 10)
}
setShowToast(showToast: boolean) {
this.setState({ showToast })
}
private renderModal() {
const { isOpen } = this.state
const { product, history } = this.props
const data = {
isOpen,
product
}
const routeInfo = `/vendor/${product.providerId}/product/${product._id}`
history.push(routeInfo, data)
}
validateIfExistLadders = (ladder:any) => ladder && ladder.length > 0
isLongName = (name: string) => name.length >= 25
render() {
const { product, hidePrice } = this.props
const { isOpen, nameToRender, effectivePrice, offerPrice } = this.state
const { filename, brand, ladder } = product
return (
<Fragment>
{isOpen && this.renderModal()}
<div className="product-card" onClick={() => this.onProductClick()}>
<div className="header" ></div>
<div className="body">
<div className="picture" style={{ backgroundImage: `url(${filename})` }}>
{ this.validateIfExistLadders(ladder) &&
<img className="img-ladder" src={small_ladder} alt="Escalonado"/>
}
</div>
<div className="tp">{brand}</div>
<div className="tp" >{nameToRender}</div>
</div>
{offerPrice && !hidePrice ? (
<div className="footer offer">
<Fragment>
<IonText className="tp-old">
<s>{asClp(effectivePrice)}</s>
</IonText>
<IonText className="tp-offer">{asClp(offerPrice)}</IonText>
</Fragment>
</div>
) : (
!hidePrice &&
<div className="footer">
<IonText>{asClp(effectivePrice)}</IonText>
</div>
)}
</div>
</Fragment>
)
}
}
export default track({page: 'ProductCard'})(ProductCard)
I am using the react-select library with a list of states, and then using that to hide/show my elements
The elements are showing when I select them from the list, but then don't hide when they're removed. Here is the code:
import React from 'react';
import Select from 'react-select';
const options = [
{ value: 'ny', label: 'NY' },
{ value: 'ca', label: 'California' },
{ value: 'ak', label: 'Arkansas' }
];
export default class HomePage extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
handleChange = (selectedOption) => {
if (!selectedOption) {
this.setState({})
}
else {
const result = {}
selectedOption.map((option) => {
result[option.value] = true
})
this.setState(result)
}
}
render() {
return (
<div>
<Select
isMulti
onChange={this.handleChange}
options={options}
/>
{this.state.ny && (
<div>NY State Images</div>
)}
{this.state.ca && (
<div>CA State Images</div>
)}
{this.state.ak && (
<div>AK State Images</div>
)}
</div>
)
}
}
Something is strange with that React.Component.
Try to use functional component:
export default function HomePage() {
const [state, setState] = useState({});
const handleChange = selectedOption => {
console.log("CHANGE_HAPPEN: ", selectedOption);
if (!selectedOption) {
setState({});
} else {
const result = {};
selectedOption.forEach(option => {
result[option.value] = true;
});
console.log("RESULT: ", result);
setState(prev => result);
}
};
return (
<div>
<Select isMulti onChange={handleChange} options={options} />
{state.ny && <div>NY State Images</div>}
{state.ca && <div>CA State Images</div>}
{state.ak && <div>AK State Images</div>}
</div>
);
}
I am facing such problem, i got my array of records fetched from an API, mapped it into single elements and outputting them as single components. I have function which changes state of parent Component, passes value to child component and child component should hide/show div content after button is clicked.
Of course. It is working, but partially - my all divs are being hidden/shown. I have set specific key to each child component but it doesn't work.
App.js
import React, { Component } from 'react';
import './App.css';
import axios from 'axios';
import countries from '../../countriesList';
import CitySearchForm from './CitySearchForm/CitySearchForm';
import CityOutput from './CityOutput/CityOutput';
import ErrorMessage from './ErrorMessage/ErrorMessage';
class App extends Component {
state = {
country: '',
error: false,
cities: [],
infoMessage: '',
visible: false
}
getCities = (e) => {
e.preventDefault();
const countryName = e.target.elements.country.value.charAt(0).toUpperCase() + e.target.elements.country.value.slice(1);
const countryUrl = 'https://api.openaq.org/v1/countries';
const wikiUrl ='https://en.wikipedia.org/w/api.php?action=query&prop=extracts&exintro&explaintext&format=json&category=city&redirects&origin=*&titles=';
const allowedCountries = new RegExp(/spain|germany|poland|france/, 'i');
if (allowedCountries.test(countryName)) {
axios
.get(countryUrl)
.then( response => {
const country = response.data.results.find(el => el.name === countryName);
return axios.get(`https://api.openaq.org/v1/cities?country=${country.code}&order_by=count&sort=desc&limit=10`)
})
.then( response => {
const cities = response.data.results.map(record => {
return { name: record.city };
});
cities.forEach(city => {
axios
.get(wikiUrl + city.name)
.then( response => {
let id;
for (let key in response.data.query.pages) {
id = key;
}
const description = response.data.query.pages[id].extract;
this.setState(prevState => ({
cities: [...prevState.cities, {city: `${city.name}`, description}],
infoMessage: prevState.infoMessage = ''
}))
})
})
})
.catch(error => {
console.log('oopsie, something went wrong', error)
})
} else {
this.setState(prevState => ({
infoMessage: prevState.infoMessage = 'This is demo version of our application and is working only for Spain, Poland, Germany and France',
cities: [...prevState.cities = []]
}))
}
}
descriptionTogglerHandler = () => {
this.setState((prevState) => {
return { visible: !prevState.visible};
});
};
render () {
return (
<div className="App">
<ErrorMessage error={this.state.infoMessage}/>
<div className="form-wrapper">
<CitySearchForm getCities={this.getCities} getInformation={this.getInformation} countries={countries}/>
</div>
{this.state.cities.map(({ city, description }) => (
<CityOutput
key={city}
city={city}
description={description}
show={this.state.visible}
descriptionToggler={this.descriptionTogglerHandler} />
))}
</div>
);
}
}
export default App;
CityOutput.js
import React, { Component } from 'react';
import './CityOutput.css';
class CityOutput extends Component {
render() {
const { city, descriptionToggler, description, show } = this.props;
let descriptionClasses = 'output-record description'
if (show) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={descriptionToggler}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
export default CityOutput;
Put the visible key and the toggle function in the CityOutput instead of having it in the parent
import React, { Component } from "react";
import "./CityOutput.css";
class CityOutput extends Component {
state = {
visible: true
};
descriptionTogglerHandler = () => {
this.setState({ visible: !this.state.visible });
};
render() {
const { city, description } = this.props;
let descriptionClasses = "output-record description";
if (this.state.visible) {
descriptionClasses = "output-record description open";
}
return (
<div className="output">
<div className="output-record">
<b>City:</b> {city}
</div>
<button onClick={() => this.descriptionTogglerHandler()}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
);
}
}
export default CityOutput;
There are two ways of how I would approach this,
The first one is setting in your state a key property and check and compare that key with the child keys like:
state = {
country: '',
error: false,
cities: [],
infoMessage: '',
visible: false.
currKey: 0
}
descriptionTogglerHandler = (key) => {
this.setState((prevState) => {
return { currKey: key, visible: !prevState.visible};
});
};
// then in your child component
class CityOutput extends Component {
render() {
const { city, descriptionToggler, description, show, currKey, elKey } = this.props;
let descriptionClasses = 'output-record description'
if (show && elKey === currKey) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={() => descriptionToggler(elKey)}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
The other way is to set an isolated state for every child component
class CityOutput extends Component {
constructor(props) {
this.state = {
show: false
}
}
function descriptionToggler() {
const {show} = this.state;
this.setState({
show: !show
})
}
render() {
const { city, descriptionToggler, description } = this.props;
let descriptionClasses = 'output-record description'
if (this.state.show) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={descriptionToggler}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
I hope this helps ;)
I am developing an accordion which is nested in character. I mean, an accordion can have its child accordion as well. Right now, a simple accordion has been build up. The toggling part works either. However, there is a problem. Child accordion is shown without opening the parent accordion. How can i show child accordion only when parent accordion is clicked and hide it when clicked again?
Here is what i have done
class Accordion extends React.Component {
constructor(props) {
super(props);
let state = { activeSections: {} };
React.Children.toArray(props.children).forEach(child => {
if (child) {
state.activeSections[child.props.name] = !!child.props.defaultOpen;
}
});
this.state = state;
}
get isControlled() {
return typeof this.props.onToggle === "function";
}
expandAll = () => {
if (this.isControlled) {
this.props.expandAll();
} else {
let { activeSections } = this.state;
Object.keys(activeSections).forEach(k => (activeSections[k] = true));
this.setState({ activeSections });
}
};
collapseAll = () => {
if (this.isControlled) {
this.props.collapseAll();
} else {
let { activeSections } = this.state;
Object.keys(activeSections).forEach(k => (activeSections[k] = false));
this.setState({ activeSections });
}
};
onToggle = name => {
if (this.isControlled) {
this.props.onToggle(name);
} else {
let activeSections = this.state.activeSections;
this.setState({
activeSections: { ...activeSections, [name]: !activeSections[name] }
});
}
};
componentWillReceiveProps(nextProps) {
let { activeSections } = this.state;
React.Children.toArray(nextProps.children)
.filter(c => c)
.forEach(child => {
if (activeSections[child.props.name] == null) {
activeSections[child.props.name] = !!child.props.defaultOpen;
}
});
this.setState({ activeSections });
}
render() {
let { activeSections } = this.state;
let children = React.Children.toArray(this.props.children);
// if (!children.find(c => c && c.type === AccordionHeader)) {
// children = [<AccordionHeader />, ...children];
// }
console.log("children", children);
return (
<div>
{children.map(child => {
if (!child) {
return child;
} else if (child.type === AccordionItem) {
return React.cloneElement(child, {
expanded: this.isControlled
? child.props.expanded
: activeSections[child.props.name],
onToggle: this.onToggle
});
} else {
return child;
}
})}
</div>
);
}
}
export default Accordion;
class AccordionItem extends React.Component {
render() {
let {
expanded,
caption,
onToggle,
name,
children,
render,
...rest
} = this.props;
return render ? (
render({ onToggle: onToggle.bind(null, name), expanded })
) : (
<styled.AccordionItem style={{ margin: 10 }}>
<styled.AccordionHeader
onClick={() => onToggle(name)}
active={expanded}
>
{caption}
</styled.AccordionHeader>
<styled.AccordionBody active={rest.defaultOpen || expanded}>
{children && (
<styled.AccordionBodyContent>
{children}
</styled.AccordionBodyContent>
)}
</styled.AccordionBody>
</styled.AccordionItem>
);
}
}
export default AccordionItem;
I have create a working example in the sandbox and here it is
https://codesandbox.io/s/z25j8q2v4m
I have this component that renders multi choice checkboxes and it works well. But I would like to have the option to specify that only one checkbox can be checked.
How can I change this component to be that dynamic
I am using this component with redux-form lib
Code
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
class CheckboxGroup extends PureComponent {
constructor(props) {
super(props)
this.checkboxGroup = this.checkboxGroup.bind(this)
}
checkboxGroup() {
const {
id,
options,
input,
} = this.props
return options.map(option => (
<div key={option.id}>
<div className="checkbox-container">
<input
type="checkbox"
id={id}
name={`${input.name}[${option.id}]`}
value={option.name}
checked={input.value.indexOf(option.name) !== -1}
onChange={(event) => {
const newValue = [...input.value]
if (event.target.checked) {
newValue.push(option.name)
} else {
newValue.splice(newValue.indexOf(option.name), 1)
}
return input.onChange(newValue)
}}
/>
{option.name && <span>{option.name}</span>}
</div>
</div>
))
}
render() {
const { label, id } = this.props
return (
<div className="input-container">
<label htmlFor={id}>
<span>{label}</span>
</label>
{this.checkboxGroup()}
</div>
)
}
}
CheckboxGroup.propTypes = {
options: PropTypes.arrayOf(PropTypes.object),
input: PropTypes.shape({
}).isRequired,
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
}
export default CheckboxGroup
You'll need to use a normal React.Component instead of a React.PureComponent. After you've changed that you need to maintain a "component state". It would be similar to the following example code.
class CheckboxGroup extends React.Component {
constructor(props) {
super(props)
this.state = {
selected: []
}
}
handleClick(id) {
return (ev) => {
const {single} = this.props
const selectedIds = this.state.selected
if (single) {
// there can only be one active id
if (selectedIds.length !== 0) {
// check if it's the current id
if (selectedIds[0] === id) {
// it's the current id, remove the id
this.setState({
selected: []
})
}
} else {
// there is no id selected, add the current id
this.setState({
selected: [id]
})
}
} else {
const selected = selectedIds.slice()
const index = = selectedIds.indexOf(id)
// check if the current id is already selected
if (index !== -1) {
// current id is not selected, add the current id
selected.push(id)
} else {
// current id is selected, remove the current id
selected.splice(index, 1)
}
this.setState({
selected
})
}
}
}
render() {
const {options, input, id} = this.props
const {selected} = this.state
options.map((option, index) => (
<div key={option.id}>
<div className="checkbox-container">
<input
type="checkbox"
id={id}
name={input.name + '[' + option.id + ']'}
value={option.name}
checked={selected.contains(option.id)}
onChange={this.handleClick(option.id)}
/>
{option.name && <span>{option.name}</span>}
</div>
</div>
))
}
}