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>
))
}
}
Related
I am implementing a setting page for an application. For each setting I have implemented a slider that has enabled(green) or disabled(red) state. But parent's settings is read only and is calculated based on the values of its children.
Parent's setting is derived as follows: If all children are red, parent stays red ; If all are green parent stays green; If at-least one of child is green then parent stays grey(Pending).
These settings are grouped something like this:
Parent Feature 1 : (read-only-toggle)
Setting 1 (Toggle)
Setting 2 (Toggle)
Parent Feature 2: (read-only-toggle)
Setting 1 (Toggle)
Setting 2 (Toggle)
And in the end there is also a button, that gives me a consolidated values of all parent and children. But so far I was able to do only with one parent and 2 children.
Can someone help with an approach of getting consolidated values of all the settings in one place(Like a super parent component where all these settings are configured).
For this , I am using react-multi-toggle for this toggle switch.
Help would be really appreciated.
Code Sandbox: https://codesandbox.io/s/react-multi-toggle-solution-perfect-v9bi5
App
import React from "react";
import ChildSwitch from "./ChildSwitch";
import ParentSwitch from "./ParentSwitch";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
parentVal: "disabled",
switch1Val: "enabled",
switch2Val: "disabled"
};
}
componentDidMount() {
this.setParentSwitchValue();
}
onGetChildSwitchValues = () => {
console.log(this.state);
};
setChildSwitchValue = (whichSwitch, selected) => {
this.setState(
prevState => ({ ...prevState, [whichSwitch]: selected }),
this.setParentSwitchValue
);
};
setParentSwitchValue = () => {
const { switch1Val, switch2Val } = this.state;
const switchStates = [switch1Val, switch2Val];
let parent = "pending";
if (switchStates.every(val => val === "enabled")) {
parent = "enabled";
}
if (switchStates.every(val => val === "disabled")) {
parent = "disabled";
}
this.setState(prevState => ({ ...prevState, parentVal: parent }));
};
render() {
const { parentVal, switch1Val, switch2Val } = this.state;
return (
<>
<div className="boxed">
Parent Setting 1 :{" "}
<ParentSwitch
parentSwitch={parentVal}
onSelect={this.setParentSwitchValue}
/>
Setting 1:
<ChildSwitch
switchName={"switch1Val"}
selected={switch1Val}
onSelect={this.setChildSwitchValue}
/>
Setting 2:
<ChildSwitch
switchName={"switch2Val"}
selected={switch2Val}
onSelect={this.setChildSwitchValue}
/>
</div>
<button onClick={this.onGetChildSwitchValues}>Get All Values</button>
</>
);
}
}
ChildSetting
import MultiToggle from "react-multi-toggle";
import React from "react";
export default class ChildSwitch extends React.Component {
constructor(props) {
super(props);
this.state = {
options: [
{
displayName: "Disabled",
value: "disabled"
},
{
displayName: "Enabled",
value: "enabled"
}
]
};
}
onSelectOption = selected => {
this.props.onSelect(this.props.switchName, selected);
};
render() {
const { options } = this.state;
const { selected } = this.props;
return (
<MultiToggle
options={options}
selectedOption={selected}
onSelectOption={this.onSelectOption}
/>
);
}
}
Parent Setting
import MultiToggle from "react-multi-toggle";
import React from "react";
import "react-multi-toggle/style.css";
export default class ParentSwitch extends React.Component {
constructor(props) {
super(props);
this.state = {
options: [
{
displayName: "Disabled",
value: "disabled"
},
{
displayName: "Pending",
value: "pending"
},
{
displayName: "Enabled",
value: "enabled"
}
]
};
}
render() {
const { options } = this.state;
return (
<MultiToggle
options={options}
selectedOption={this.props.parentSwitch}
onSelectOption={() => {}}
/>
);
}
}
I will suggest that you group your child and parent under one component. Let say we name it Settings. Then, we create another component that will render a list of Settings and a button. This last component will hold the values of all Settings. Finally, each time the value of a Setting Component Change, we update the list. Checkout a sample working app here.
App Component
export default class App extends PureComponent {
state = {};
onSettingChange = (settingId, setting) => {
this.setState(prevState => ({
...prevState,
[settingId]: setting
}));
};
onGetSettingValues = () => {
console.log(this.state);
};
render() {
return (
<Fragment>
<Setting id="setting1" onChange={this.onSettingChange} />
<Setting id="setting2" onChange={this.onSettingChange} />
<button onClick={this.onGetSettingValues}>Get All Values</button>
</Fragment>
);
}
}
Setting Component
import React, { PureComponent, Fragment } from "react";
import ChildSwitch from "./ChildSwitch";
import ParentSwitch from "./ParentSwitch";
export default class Setting extends PureComponent {
state = {
parentVal: "disabled",
switch1Val: "enabled",
switch2Val: "disabled"
};
componentDidMount() {
this.setParentSwitchValue();
}
setChildSwitchValue = (whichSwitch, selected) => {
this.setState(
prevState => ({ ...prevState, [whichSwitch]: selected }),
this.setParentSwitchValue
);
};
handleChange = () => {
const { id, onChange } = this.props;
onChange(id, this.state);
};
setParentSwitchValue = () => {
const { switch1Val, switch2Val } = this.state;
const switchStates = [switch1Val, switch2Val];
let parent = "pending";
if (switchStates.every(val => val === "enabled")) {
parent = "enabled";
}
if (switchStates.every(val => val === "disabled")) {
parent = "disabled";
}
this.setState(
prevState => ({ ...prevState, parentVal: parent }),
this.handleChange
);
};
render() {
const { parentVal, switch1Val, switch2Val } = this.state;
return (
<Fragment>
<div className="boxed">
Parent Setting 1
<ParentSwitch
parentSwitch={parentVal}
onSelect={this.setParentSwitchValue}
/>
Setting 1:
<ChildSwitch
switchName={"switch1Val"}
selected={switch1Val}
onSelect={this.setChildSwitchValue}
/>
Setting 2:
<ChildSwitch
switchName={"switch2Val"}
selected={switch2Val}
onSelect={this.setChildSwitchValue}
/>
</div>
</Fragment>
);
}
}
Put all your states into a single context hook.
const SettingsContext = createContext({state1, state2/* all your states in here*/);
You'll then wrap the whole thing into this context as such:
<SettingsContext.Provider>
<App/>
</SettingsContext.Provider>
Now you can access the state in any of the children, parents etc. I suggest however not storing things like "disabled", "enabled" as strings, but rather store states as { enabled: true, pending: false}
I'm trying to use non state variable 'newItem' to hold the input value
import React, { Component } from 'react';
class List extends Component {
constructor() {
super();
this.state = { items: [1, 2] };
this.newItem = undefined;
}
changeNewItem = e => {
this.newItem = e.target.value;
console.log(this.newItem);
};
addItem = e => {
if (e.keyCode !== 13) return;
var tmp_list = this.state.items;
tmp_list.push(this.newItem);
this.setState({ items: tmp_list }, () => {
this.newItem = '';
});
};
render() {
return (
<div>
<ul>
{this.state.items.map(item => (
<li key={item}>{item}</li>
))}
</ul>
<input
type="text"
placeholder="add item"
value={this.newItem}
onChange={this.changeNewItem}
onKeyUp={this.addItem}
/>
</div>
);
}
}
export default List;
When I press enter key in textbox the item gets added to the array but getting error as below
index.js:1452 Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info:
in input (at List.js:29)
in div (at List.js:23)
in List (at App.js:9)
in App (at src/index.js:8)
The problem in your case is that the initial value of the input element is undefined and then you control it using the variable this.newItem. Hence you get the warning which is trying to change an uncontrolled input to controlled.
Initialise the value to empty string instead of undefined. Also if you want the input component to change value, use state instead of a class variable
import React, { Component } from 'react';
class List extends Component {
constructor() {
super();
this.state = { items: [1, 2], newItem: '' };
}
changeNewItem = e => {
this.setState({newItem: e.target.value})
};
addItem = e => {
if (e.keyCode !== 13) return;
var tmp_list = this.state.items;
tmp_list.push(this.state.newItem);
this.setState({ items: tmp_list }, () => {
this.state.newItem = '';
});
};
render() {
return (
<div>
<ul>
{this.state.items.map(item => (
<li key={item}>{item}</li>
))}
</ul>
<input
type="text"
placeholder="add item"
value={this.state.newItem}
onChange={this.changeNewItem}
onKeyUp={this.addItem}
/>
</div>
);
}
}
export default List;
changed to uncontrolled input and usedReact.createRef(), suggested by Comment
import React, { Component } from 'react';
class List extends Component {
newItem;
constructor() {
super();
this.state = { items: [1, 2] };
this.input = React.createRef();
}
changeNewItem = e => {
this.newItem = e.target.value;
console.log(this.newItem);
};
addItem = e => {
if (e.keyCode !== 13 || !this.newItem) return;
var new_list = this.state.items.concat(this.newItem);
this.setState({ items: new_list }, () => {
this.newItem = '';
this.input.current.value = '';
});
};
render() {
return (
<div>
<ul>
{this.state.items.map(item => (
<li key={item}>{item}</li>
))}
</ul>
<input
type="text"
placeholder="add item"
ref={this.input}
onChange={this.changeNewItem}
onKeyUp={this.addItem}
/>
</div>
);
}
}
export default List;
I have a .map() function where I'm iterating over an array and rendering elements, like so:
{options.map((option, i) => (
<TachyonsSimpleSelectOption
options={options[i]}
key={i}
onClick={() => this.isSelected(i)}
selected={this.toggleStyles("item")}
/>
I am toggling the state of a selected element like so:
isSelected (i) {
this.setState({ selected: !this.state.selected }, () => { console.log(this.state.selected) })
}
Using a switch statement to change the styles:
toggleStyles(el) {
switch (el) {
case "item":
return this.state.selected ? "bg-light-gray" : "";
break;
}
}
And then passing it in my toggleStyles method as props to the className of the TachyonsSimpleSelectOption Component.
Problem
The class is being toggled for all items in the array, but I only want to target the currently clicked item.
Link to Sandbox.
What am I doing wrong here?
You're using the selected state incorrectly.
In your code, to determine whether it is selected or not, you depends on that state, but you didn't specify which items that is currently selected.
Instead saving a boolean state, you can store which index is currently selected so that only specified item is affected.
This may be a rough answer, but I hope I can give you some ideas.
on your render:
{options.map((option, i) => (
<TachyonsSimpleSelectOption
options={options[i]}
key={i}
onClick={() => this.setState({ selectedItem: i })}
selected={this.determineItemStyle(i)}
/>
))}
on the function that will determine the selected props value:
determineItemStyle(i) {
const isItemSelected = this.state.selectedItem === i;
return isItemSelected ? "bg-light-gray" : "";
}
Hope this answer will give you some eureka moment
You are not telling react which element is toggled. Since the state has just a boolean value selected, it doesn't know which element is selected.
In order to do that, change your isSelected function to :
isSelected (i) {
this.setState({ selected: i }, () => {
console.log(this.state.selected) })
}
Now, the React state knows that the item on index i is selected. Use that to toggle your class now.
In case you want to store multiple selected items, you need to store an array of indices instead of just one index
TachyonsSimpleSelectOption.js:
import React from 'react';
class Option extends React.Component {
render() {
const { selected, name } = this.props;
return(
<h1
onClick={() => this.props.onClick()}
style={{backgroundColor: selected ? 'grey' : 'white'}}
>Hello {name}!</h1>
)
}
}
export default Option;
index.js:
import React from "react";
import { render } from "react-dom";
import TachyonsSimpleSelectOption from "./TachyonsSimpleSelectOption";
const options = ["apple", "pear", "orange"];
const styles = {
selected: "bg-light-gray"
};
class Select extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
selected: []
};
this.handleClick = this.handleClick.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.isSelected = this.isSelected.bind(this);
}
handleBlur() {
this.toggleMenu(close);
}
handleClick(e) {
this.toggleMenu();
}
toggleMenu(close) {
this.setState(
{
open: !this.state.open
},
() => {
this.toggleStyles("menu");
}
);
}
toggleStyles(el, index) {
switch (el) {
case "menu":
return this.state.open ? "db" : "dn";
break;
case "item":
const { selected } = this.state;
return selected.indexOf(index) !== -1;
break;
}
}
isSelected(i) {
let { selected } = this.state;
if (selected.indexOf(i) === -1) {
selected.push(i);
} else {
selected = selected.filter(index => index !== i);
}
this.setState({ selected});
}
render() {
const { options } = this.props;
return (
<div
className="flex flex-column ba"
onBlur={this.handleBlur}
tabIndex={0}
>
<div className="flex-row pa3" onClick={this.handleClick}>
<span className="flex-grow-1 w-50 dib">Title</span>
<span className="flex-grow-1 w-50 dib tr">^</span>
</div>
<div className={this.toggleStyles("menu")}>
{options.map((option, i) => (
<TachyonsSimpleSelectOption
name={options[i]}
key={i}
onClick={() => this.isSelected(i)}
selected={this.toggleStyles("item", i)}
/>
))}
</div>
</div>
);
}
}
render(<Select options={options} />, document.getElementById("root"));
And Link to Sandbox.
I have a parent component ProductTest that holds state as the source of truth. The state is an array of variant items. Each item is simplified for this example and has a title.
When a Variant's title is updated, the child will call it's handler callback via the prop passed by the parent. The parent then updates state, and the child will then have a new title property.
What I am seeing is that the child's componentWillReceiveProps function will display the same value for this.props and nextProps. I would think that since the value is supplied by the parent, the two would be different.
I'm not sure what I'm doing incorrectly.
Here is the parent ProductTest component:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import axios from 'axios'
import Variant from './variants/Variant'
import { REACT_APP_API_URL, REACT_APP_SITE_KEY } from '../../../shared/vars'
import '../../../css/variants.css'
class ProductTest extends Component {
constructor(props) {
super(props)
this.state = {
variants: []
}
this.handleVariantTitleChange = this.handleVariantTitleChange.bind(this)
this.handleVariantValueChange = this.handleVariantValueChange.bind(this)
}
componentDidMount() {
this.loadData()
}
loadData() {
const apiEndpoint = 'products/some-product'
const AUTH_HEADER = { Authorization: REACT_APP_SITE_KEY }
return axios.get(REACT_APP_API_URL + apiEndpoint, { headers: AUTH_HEADER })
.then((response) => {
this.setState({ variants: response.data.product.variants })
})
.catch((err) => {
console.log("YA GOOFED BUD!", err)
})
}
handleVariantTitleChange(e, i) {
let { variants } = this.state
const { value } = e.target
variants = variants.map((item, index) => {
item.title = i === index ? value : item.title
return item
})
this.setState({ variants })
}
handleVariantValueChange(e, id) {
let { variants } = this.state
const { value } = e.target
variants = variants.map(variant => {
variant.items = variant.items.map(item => {
item.title = id === item.id ? value : item.title
return item
})
return variant
})
this.setState({ variants })
}
render() {
return (
<div>
{this.state.variants.map((variant, i) => {
return <Variant key={i}
variant={variant}
index={i}
changeVariantTitle={this.handleVariantTitleChange}
changeVariantValue={this.handleVariantValueChange}
/>
})}
</div>
)
}
}
export default ProductTest
Here is the child Variant component:
import React, { Component } from 'react'
class Variant extends Component {
componentWillReceiveProps(nextProps){
console.log(this.props, nextProps)
}
render() {
const { variant, index } = this.props
return (
<div className="variant-wrapper" data-new="new_value">
<div className="ajax-error" data-hbs-id="{{id}}"></div>
<div className="varient-item-titles">
<div className="variant-item-title">
Title
</div>
<div className="varient-attribute-titles">
<div className="variant-attribute">Value</div>
</div>
</div>
<div className="variant-item-content">
<div className="variant-label">
<input type="text" name="variant_label" value={variant.title} placeholder="Size, colour, etc."
onChange={e => this.props.changeVariantTitle(e, index)}
/>
</div>
</div>
</div>
)
}
}
export default Variant
Before
After
Console.log()
These two objects are this.props and nextProps. Notice the title property is not different, as I'd expect them to be.
SOLUTION!
The reason for all of those is that I was not correctly altering the parent's state immutably. I think I was just changing the value at the same memory address, and hence by the time it got to the componentWillReceiveProps it was already the new value. I think...
I found that the following will work:
handleVariantTitleChange(e, i){
const { value } = e.target
// Immutably clone specific variant item
let variant = Object.assign({}, this.state.data.variants[i])
// Set new title to item
variant.title = value
// Set the variant to the cloned item
let variants = this.state.data.variants
variants[i] = variant
this.setState({
data: {
...this.state.data,
variants
}
})
}
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>