React Checkbox Group - Set initial state from API - reactjs

I have a CheckboxGroup component which takes in an options array prop and generates CheckboxInput components. On page load I make a call to an API which returns an array of pre-selected checkboxes (delivered to the value prop). Depending on the logged in user, this call can return an empty array or a selection of previously selected checkbox options.
The following code successfully takes the response of the API call and sets the relevant checkboxes to 'checked'. The issue I have is that this code doesn't allow me to make changes to the checkboxes after page load (clicking a checkboxes has no effect).
I think there is also some disconnect between the initial selectedCheckboxes state and the value of the API call but I read that setting props as initial state is an anti-pattern (e.g. selectedCheckboxes: props.value,).
export default class CheckboxGroup extends Component {
constructor(props) {
super(props);
this.state = {
selectedCheckboxes: [],
};
}
addCheckboxToSelected = (id) => {
if (this.state.selectedCheckboxes.includes(id)) {
// Remove checkbox from array and update state
const filteredArray = this.state.selectedCheckboxes.filter(item => item !== id);
this.setState({ selectedCheckboxes: filteredArray });
} else {
// Add checkbox to array and update state
this.setState({ selectedCheckboxes: this.state.selectedCheckboxes.concat(id) });
}
}
checkIfSelected = (checkboxValue) => {
const preSelectedCheckboxes = this.props.value;
let selectedBool = false;
preSelectedCheckboxes.some(function(object) {
if (object.id === checkboxValue) {
selectedBool = true;
}
return false;
});
return selectedBool;
}
render() {
const { label, name, options } = this.props;
return (
<div className="form-group form-inline">
<span className="checkboxgroup-heading">{label}</span>
<div className="form-group-container">
{options.map(object => (
<CheckboxInput
key={object.value}
name={name}
label={object.label}
onChange={this.addCheckboxToSelected}
value={object.value}
checked={this.checkIfSelected(object.value)}
/>
))}
</div>
</div>
);
}
}
This is the stateless CheckboxInput component
const CheckboxInput = ({ name, label, onChange, value, checked }) => {
return (
<div className="field form-group filter-input">
<input
type="checkbox"
id={value}
name={name}
value={value}
onChange={() => onChange(value)}
checked={checked}
/>
<label htmlFor={value} className="form-control">{label}</label>
</div>
);
};

Check the following code snippet. This might help. Let me know if you have questions.
const CheckboxField = ({checked, onChange}) => {
return (
<input type="checkbox" checked={checked} onChange={ev => onChange(ev.target.checked)} />
);
};
class App extends React.Component {
constructor() {
super();
this.state = {
options: [{id: "1", checked: true}, {id: "2", checked: false}]
};
}
handleCheckboxChange(checked, option) {
const {options} = this.state;
var cOptions = [...options];
for(var i in cOptions) {
if(cOptions[i].id == option.id) {
cOptions[i].checked = checked;
}
}
this.setState({
options: cOptions
}, () => console.log(options));
}
render() {
const {options} = this.state;
return (
<div>
{
options.map(option => {
return (
<CheckboxField key={option.id} checked={option.checked} onChange={value => this.handleCheckboxChange(value, option)} />
)
})
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<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>

Related

Having trouble updating state in React after API call

I am trying to create a textbox with an autocomplete feature that pulls suggestions from an API but having trouble updating the state after I receive the array from the API. I am modifying code from here: https://blog.bitsrc.io/building-a-react-autocomplete-component-from-scratch-b78105324f4c
I think I have to use ComponentDidMount() but I am not sure how to apply it to an onChange Function.
class App extends React.Component {
render() {
return (
<div className="App">
<Autocomplete/>
</div>
);
}
}
class Autocomplete extends React.Component{
state = {
activeOption: 0,
filteredOptions: [],
showOptions: false,
userInput: ''
};
onChange = (e) => {
const userInput = e.currentTarget.value;
fetch("/places", {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(userInput)
}).
then(response => response.json())
.then(data => data.filter(element =>
element.PlaceName.toLowerCase().indexOf(userInput.toLowerCase()) > -1))
.then(filteredOptions => this.setState(
{
activeOption: 0,
filteredOptions: filteredOptions,
showOptions: true,
userInput: e.currentTarget.value
}));
};
.
.
.
.
render() {
const {
onChange,
onClick,
onKeyDown,
state: { activeOption, filteredOptions, showOptions, userInput }
} = this;
let optionList;
if (showOptions && userInput) {
console.log(filteredOptions)
if (filteredOptions.length) {
optionList = (
<ul className="options">
{filteredOptions.map((optionName, index) => {
let className;
if (index === activeOption) {
className = 'option-active';
}
return (
<li className={className} key={optionName} onClick={onClick}>
{optionName}
</li>
);
})}
</ul>
);
} else {
optionList = (
<div className="no-options">
<em>No Option!</em>
</div>
);
}
}
return (
<React.Fragment>
<div className="search">
<input
type="text"
className="search-box"
onChange={onChange}
onKeyDown={onKeyDown}
value={userInput}
/>
<input type="submit" value="" className="search-btn" />
</div>
{optionList}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.querySelector("#app"));
Once I try and run this, I get two errors: one for a synthetic event being reused for performance issues and one for a component changing an controlled input
You could put a setTimeout in the onChange method. And if the user doesn't type, you make the request, in other wise, you can't make the request
It looks like you were anticipating the filteredOptions variable to be a list of strings. It is actually a list of objects, which was causing React to throw the "object not allowed as children" error. Also, you can't use the event object to set state as it is already released. However, you were already storing the value in a variable which you could use, userInput. I updated your code with some very minor tweaks, and it appears to work. Take a look at a working example:
import React from "react";
import "./styles.css";
class App extends React.Component {
render() {
return (
<div className="App">
<Autocomplete />
</div>
);
}
}
class Autocomplete extends React.Component {
state = {
activeOption: 0,
filteredOptions: [],
showOptions: false,
userInput: ""
};
onChange = (e) => {
const userInput = e.currentTarget.value;
// Mock out the API call and JSON
Promise.resolve()
.then(() => {
const data = [
{ PlaceName: "Place 1" },
{ PlaceName: "Place 2" },
{ PlaceName: "Another Place 1" },
{ PlaceName: "Another Place 2" }
];
return data.filter(
(element) =>
element.PlaceName.toLowerCase().indexOf(userInput.toLowerCase()) >
-1
);
})
.then((filteredOptions) =>
this.setState({
activeOption: 0,
filteredOptions: filteredOptions,
showOptions: true,
userInput: userInput
})
);
};
render() {
const {
onChange,
onClick,
onKeyDown,
state: { activeOption, filteredOptions, showOptions, userInput }
} = this;
let optionList;
if (showOptions && userInput) {
if (filteredOptions.length) {
optionList = (
<ul className="options">
{filteredOptions.map((option, index) => {
let className;
if (index === activeOption) {
className = "option-active";
}
return (
<li
className={className}
key={option.PlaceName}
onClick={onClick}
>
{option.PlaceName}
</li>
);
})}
</ul>
);
} else {
optionList = (
<div className="no-options">
<em>No Option!</em>
</div>
);
}
}
return (
<React.Fragment>
<div className="search">
<input
type="text"
className="search-box"
onChange={onChange}
onKeyDown={onKeyDown}
value={userInput}
/>
<input type="submit" value="" className="search-btn" />
</div>
{optionList}
</React.Fragment>
);
}
}
export default App;

How to Dynamically set the attributes of a component, from users input?

Set the attributes of a input field or any component by taking input from the user dynamically?
I would like to know if there is any way, where I would give user an option to choose a component from the list of components i would mention, and allow him to customize the components attributes. For example if the user chooses a Input component, he must be able to set the attributes of that particular component, like "required", "type", "placeholder".
You can achieve it by passing all attributes you want as props to the child component.
You should also add them to state of parent component with change handler.
Each time the user change something of the attributes, the state should update.
As the state updates, the new state will pass as props to child Component and it'll update.
I made a simple example to input: You can change its placeholder, minLength, and requierd.
Check This Example
in the render, method you can do something like this
render() {
// change the name and values base on your user input
userInputtedAttribName = "placeholder";
userInputtedAttribValue = "the placeholder";
// the object to contain your user defined attribute name and values
const dynamicAttributes = {
[userInputtedAttribName]: userInputtedAttribValue
};
return (
<div>
<input type="text" {...dynamicAttributes}></input>
</div>
)
}
the spread operator, {...dynamicAttributes}, will build the attributes and their values dynamically
Probably not even what you're looking for, but I made a medium-sized prototype that can show you how to create Components (input, button, textarea), dynamically.
It's like filling out a form. Choose a type of component you want to make from the select-list. Then define the attributes you want in the proceeding textboxes. Once you're done adding all the attributes, hit Generate to render your customized component.
Sandbox: https://codesandbox.io/s/dynamic-component-generator-mhuh5
Working code:
import React from "react";
import ReactDOM from "react-dom";
import Input from "./Input";
import Button from "./Button";
import TextArea from "./TextArea";
import "./styles.css";
class ComponentGenerator extends React.Component {
state = {
componentInProgress: null,
customizeMode: false,
attributeName: "",
attributeSetting: "",
boolean: false,
builtComponents: []
};
handleSelection = e => {
this.setState({
componentInProgress: { componentType: e.target.value },
customizeMode: true
});
};
createOptions = () => {
const { customizeMode, componentInProgress } = this.state;
return (
<div>
<h4>Choose a Component:</h4>
<select
onChange={this.handleSelection}
value={!customizeMode ? "Select" : componentInProgress.componentType}
>
<option>Select</option>
<option>Input</option>
<option>TextArea</option>
<option>Button</option>
</select>
</div>
);
};
handleOnChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
handleOnSubmit = e => {
const {
attributeName,
attributeSetting,
boolean,
componentInProgress
} = this.state;
e.preventDefault();
let componentCopy = JSON.parse(JSON.stringify(componentInProgress));
componentCopy.props = {
...componentCopy.props,
[attributeName]: boolean ? boolean : attributeSetting
};
this.setState({
componentInProgress: componentCopy,
attributeName: "",
attributeSetting: "",
boolean: false
});
};
setBoolean = boolean => {
this.setState({
boolean: boolean
});
};
generateComponent = () => {
const { componentInProgress, builtComponents } = this.state;
this.setState({
componentInProgress: null,
customizeMode: false,
builtComponents: [...builtComponents, componentInProgress]
});
};
defineComponentAttributes = () => {
const {
componentInProgress,
attributeName,
attributeSetting,
boolean
} = this.state;
return (
<div>
<h4>
Customizing:{" "}
<span className="highlight">{componentInProgress.componentType}</span>
</h4>
{/*Render form */}
<form onSubmit={this.handleOnSubmit}>
<label>Attribute: </label>
<input
className="form-group"
onChange={this.handleOnChange}
value={attributeName}
name="attributeName"
placeholder="Choose attribute (type)"
/>
<label>Definition: </label>
<input
className="form-group"
onChange={this.handleOnChange}
value={attributeSetting}
name="attributeSetting"
placeholder="Define attribute (text)"
/>
<label>This is a Boolean type: </label>
<input
type="radio"
name="boolean"
onChange={() => this.setBoolean(true)}
/>
True
<input
type="radio"
name="boolean"
checked={boolean === false}
onChange={() => this.setBoolean(false)}
/>
False
<button className="form-group" type="submit">
Add
</button>
</form>
{/*Create List of attributes */}
{componentInProgress.props && (
<div>
<h4>Defined Attributes:</h4>
{Object.entries(componentInProgress.props).map(
([propName, propValue]) => {
return (
<div key={propName}>
<span>{propName}: </span>
<span>{propValue + ""}</span>
</div>
);
}
)}
</div>
)}
<div>
<h4>Click to finish and generate:</h4>
<button onClick={this.generateComponent}>Generate</button>
</div>
</div>
);
};
renderComponents = () => {
const { builtComponents } = this.state;
return builtComponents.map((component, index) => {
let renderedComponent = () => {
switch (component.componentType) {
case "Input":
return <Input {...component.props} />;
case "Button":
return <Button {...component.props} />;
case "TextArea":
return <TextArea {...component.props} />;
default:
return null;
}
};
return (
<div key={index} className="componentSection">
<h4>{component.componentType}</h4>
{renderedComponent()}
<div>
<p>Attributes: </p>
{Object.entries(component.props).map(([propName, propValue]) => {
return (
<div key={propName}>
<span>{propName}: </span>
<span>{propValue + ""}</span>
</div>
);
})}
</div>
</div>
);
});
};
render() {
const { customizeMode } = this.state;
return (
<div>
{this.createOptions()}
{customizeMode && this.defineComponentAttributes()}
<div className="components">{this.renderComponents()}</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<ComponentGenerator />, rootElement);

React handling multiple checkboxes

I made a PoC to see how to handle my change detection on a dynamic list of checkboxes (note, i do not know beforehand how many checkboxes i have.) I created a n ES6 map (Dictionary) that tracks the checked state of each individual checkbox. but for some reason I get the following error:
A component is changing an uncontrolled input of type checkbox 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
Usually when my number of form input fields is known i track them via the state, but how would one handle this case. The logic works fine, but I need to get rid of the error.
My app code:
import React, { Component } from "react";
import Checkbox from "./checkbox";
class App extends Component {
constructor(props) {
super(props);
this.state = {
checkedItems: new Map()
};
this.handleChange = this.handleChange.bind(this);
}
handleChange = e => {
const item = e.target.name;
const isChecked = e.target.checked;
this.setState(prevState => ({
checkedItems: prevState.checkedItems.set(item, isChecked)
}));
};
deleteCheckboxState = (name, checked) => {
const updateChecked = typeof checked === "undefined" ? true : false;
this.setState(prevState => prevState.checkedItems.set(name, updateChecked));
};
clearAllCheckboxes = () => {
const clearCheckedItems = new Map();
this.setState({ checkedItems: clearCheckedItems });
};
render() {
const checkboxes = [
{
name: "check-box-1",
key: "checkBox1",
label: "Check Box 1"
},
{
name: "check-box-2",
key: "checkBox2",
label: "Check Box 2"
},
{
name: "check-box-3",
key: "checkBox3",
label: "Check Box 3"
},
{
name: "check-box-4",
key: "checkBox4",
label: "Check Box 4"
}
];
const checkboxesToRender = checkboxes.map(item => {
return (
<label key={item.key}>
{item.name}
<Checkbox
name={item.name}
checked={this.state.checkedItems.get(item.name)}
onChange={this.handleChange}
type="checkbox"
/>
</label>
);
});
const checkboxesDeleteHandlers = checkboxes.map(item => {
return (
<span
key={item.name}
onClick={() =>
this.deleteCheckboxState(
item.name,
this.state.checkedItems.get(item.name)
)
}
>
{item.name}
</span>
);
});
return (
<div className="App">
{checkboxesToRender}
<br /> {checkboxesDeleteHandlers}
<p onClick={this.clearAllCheckboxes}>clear all</p>
</div>
);
}
}
export default App;
The checkbox reusable component:
import React from "react";
class Checkbox extends React.Component {
render() {
return (
<input
type={this.props.type}
name={this.props.name}
checked={this.props.checked}
onChange={this.props.onChange}
/>
);
}
}
export default Checkbox;
The problem is that the checked state is initially undefined, which is a falsy value, but interpreted as not provided.
So you can simply ensure that the falsy state will actually be false by using !!.
So change the line
checked={this.state.checkedItems.get(item.name)}
to this
checked={!!this.state.checkedItems.get(item.name)}
React gives you this warning because it likes that you chose between controlled and uncontrolled components.
In the case of a checkbox input the component is considered controlled when its checked prop is not undefined.
I've just given a default value for checked and changed the code testing for undefined a little bit.
The warning should be gone.
// import React, { Component } from "react";
class Checkbox extends React.Component {
static defaultProps = {
checked: false
}
render() {
return (
<input
type={this.props.type}
name={this.props.name}
checked={this.props.checked}
onChange={this.props.onChange}
/>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
checkedItems: new Map()
};
this.handleChange = this.handleChange.bind(this);
}
handleChange = e => {
const item = e.target.name;
const isChecked = e.target.checked;
this.setState(prevState => ({
checkedItems: prevState.checkedItems.set(item, isChecked)
}));
};
deleteCheckboxState = (name, checked) => {
const updateChecked = checked == null ? true : false;
this.setState(prevState => prevState.checkedItems.set(name, updateChecked));
};
clearAllCheckboxes = () => {
const clearCheckedItems = new Map();
this.setState({ checkedItems: clearCheckedItems });
};
render() {
const checkboxes = [
{
name: "check-box-1",
key: "checkBox1",
label: "Check Box 1"
},
{
name: "check-box-2",
key: "checkBox2",
label: "Check Box 2"
},
{
name: "check-box-3",
key: "checkBox3",
label: "Check Box 3"
},
{
name: "check-box-4",
key: "checkBox4",
label: "Check Box 4"
}
];
const checkboxesToRender = checkboxes.map(item => {
return (
<label key={item.key}>
{item.name}
<Checkbox
name={item.name}
checked={this.state.checkedItems.get(item.name) || false}
onChange={this.handleChange}
type="checkbox"
/>
</label>
);
});
const checkboxesDeleteHandlers = checkboxes.map(item => {
return (
<span
key={item.name}
onClick={() =>
this.deleteCheckboxState(
item.name,
this.state.checkedItems.get(item.name)
)
}
>
{item.name}
</span>
);
});
return (
<div className="App">
{checkboxesToRender}
<br /> {checkboxesDeleteHandlers}
<p onClick={this.clearAllCheckboxes}>clear all</p>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

React passing props from children to parent issue

I am a beginner in React. When I try to pass props from children to parent, the whole app is refreshed and the state recovery to initial. Is there any problem on my code? I have no idea how to solve it.
(ps: The following sentence is just for the number of words. Please don't see it. Why I have to add more details. If I have the ability to know every detail, I already solved it by myself)
Parent:
class App extends Component {
constructor(props) {
super(props);
this.state = {
stops: [],
legs: [],
driver: null,
finishedSign: false,
stopsSign: false,
legsSign: false,
driverSign: false
};
}
componentDidMount() {
console.log("-DID");
this.getStops();
this.getLegs();
this.getDriver();
}
// garentee all of data have received
checkFinished() {
const { stopsSign, legsSign, driverSign } = this.state;
const mark = stopsSign && legsSign && driverSign;
if (mark)
this.setState({
finishedSign: mark
});
}
// GET/STOPS API
getStops() {
fetch("/api/stops")
.then(res => res.json())
.then(stops => {
this.setState({ stops: stops, stopsSign: true }, () =>
console.log("stops fetched !", stops)
);
this.checkFinished();
});
}
// GET/LEGS API
getLegs() {
fetch("/api/legs")
.then(res => res.json())
.then(legs => {
this.setState({ legs: legs, legsSign: true }, () =>
console.log("driver fetched !", legs)
);
this.checkFinished();
});
}
// GET/Driver API
getDriver() {
console.log("-DRIVER");
fetch("/api/driver")
.then(res => {
return res.json();
})
.then(driver => {
this.setState(
{
driver: driver,
driverSign: true
},
() => console.log("driver fetched !", driver)
);
this.checkFinished();
});
}
// passing func
updateDriver(driver) {
console.log("update app!");
alert(driver);
}
renderMaps() {
return (
<Maps
stops={this.state.stops}
legs={this.state.legs}
driver={this.state.driver}
/>
);
}
renderDriverController() {
return (
<DiverController
legs={this.state.legs}
driver={this.state.driver}
update={this.updateDriver}
/>
);
}
render() {
return (
<div className="container">
<div className="row">
<div className="col-sm-3 col-md-3">
{this.state.finishedSign && this.renderDriverController()}
</div>
<div className="col-sm-8 col-md-8">
{
//this.state.finishedSign && this.renderMaps()
}
</div>
</div>
</div>
);
}
}
export default App;
children:
class DriverController extends Component {
constructor(props) {
super(props);
this.state = {
items: this.props.legs,
driver: this.props.driver
};
}
handleUpdate = e => {
const driver = null;
driver.activeLegID = this.refs.selectedLeg.value;
driver.legProgress = this.refs.selectedProgress.value;
if (driver.legProgress >= 0 && driver.legProgress <= 100)
this.props.update("test");
else alert("out of range!");
};
render() {
const { items, driver } = this.state;
console.log("items:", items);
return (
<form>
<hr />
<label>Driver Location:</label>
<div className="form-group">
<select
id="inputState"
className="form-control"
defaultValue={driver.activeLegID}
ref="selectedLeg"
>
{items.map(item => (
<option key={item.legID}>{item.legID}</option>
))}
</select>
<div className="input-group input-group-sm mb-3">
<div className="input-group-prepend">
<span className="input-group-text" id="inputGroup-sizing-sm">
Percentage:
</span>
</div>
<input
type="number"
className="form-control"
defaultValue={driver.legProgress}
ref="selectedProgress"
/>
</div>
<button onClick={this.handleUpdate} className="btn btn-primary">
Submit
</button>
<hr />
</div>
</form>
);
}
}
export default DriverController;
Try to use
onClick={() => this.handleUpdate}
You should not pass props from a child to its parent. Thats an anti-pattern.
You could pass a function from parent to child which will be triggered in
the child and hence updating the required state in the parent.
Refresh issue:
I think cause the child is wrapped inside a form.
Add
e.preventDefault() to your handleSubmit function to prevent the refresh
handleUpdate = e => {
e.preventDefault()

React checkbox in stateless component is not updating immediately

I created a basic interface using checkboxes that used a react design pattern that has served me well before and that I thought worked well - namely lifting up state and passing down props to UI components. My checkbox components are passed a value(a metric), an state-changing method, and a boolean for checked. The problem is that the checkboxes do not update immediately, even though you can see them updating in the React dev tools. They only update on the next click, as in when another checkbox is checked. Here is the code:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
metricsSelected: []
}
this.selectMetric = this.selectMetric.bind(this)
}
selectMetric(metric) {
const metricsSelected = this.state.metricsSelected
const index = metricsSelected.indexOf(metric)
if (index !== -1){
metricsSelected.splice(index, 1)
}
else {
metricsSelected.push(metric)
}
this.setState({
metricsSelected,
});
}
render() {
return (
<div>
<Sidebar
metricsSelected={this.state.metricsSelected}
selectMetric={this.selectMetric}/>
<SomethingElse/>
</div>
)
}
}
const SomethingElse = () => (<div><h2>Something Else </h2></div>)
const Sidebar = ({ metricsSelected, selectMetric }) => {
const metrics = ['first thing', 'second thing', 'third thing']
return (
<div>
<h3>Select Metrics</h3>
{ metrics.map( (metric, i) =>
<Checkbox
key={i}
metric={metric}
selectMetric={selectMetric}
checked={metricsSelected.includes(metric)}/>
)}
</div>
)
}
const Checkbox = ({ metric, selectMetric, checked }) => {
const onChange = e => {
e.preventDefault()
selectMetric(e.target.value)
}
return (
<ul>
<li>{metric}</li>
<li><input
type='checkbox'
value={metric}
checked={checked}
onChange={onChange} /></li>
</ul>
)
}
I've read pretty much everything I can get my hands on about checkboxes for react and most of the applications of the checkbox are doing something different from what I want to do. I've tried adding state to the Checkbox component, but that didn't seem to help, since the checked value still needs to come in from elsewhere. I thought react components rerendered when the props changed. What gives?
Here's a codepen: https://codepen.io/matsad/pen/QpexdM
Here is a working version: http://codepen.io/TLadd/pen/oWvOad?editors=1111
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
metricsSelected: {}
}
this.selectMetric = this.selectMetric.bind(this)
}
selectMetric(metric) {
this.setState(({ metricsSelected }) => ({
metricsSelected: {
...metricsSelected,
[metric]: !metricsSelected[metric]
}
}))
}
render() {
return (
<div>
<Sidebar
metricsSelected={this.state.metricsSelected}
selectMetric={this.selectMetric}/>
<SomethingElse/>
</div>
)
}
}
const SomethingElse = () => (<div><h2>Something Else </h2></div>)
const Sidebar = ({ metricsSelected, selectMetric }) => {
const metrics = ['first thing', 'second thing', 'third thing']
return (
<div>
<h3>Select Metrics</h3>
{ metrics.map( (metric, i) =>
<Checkbox
key={i}
metric={metric}
selectMetric={selectMetric}
checked={Boolean(metricsSelected[metric])}/>
)}
</div>
)
}
const Checkbox = ({ metric, selectMetric, checked }) => {
return (
<ul>
<li>{metric}</li>
<li>
<input
type='checkbox'
name={metric}
checked={checked}
onChange={() => selectMetric(metric)}
/>
</li>
</ul>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
The couple of things that were causing issues were that you were mutating state in selectMetric, and your checkbox input's onChange function is using e.target.value instead of e.target.checked.
I changed the metricsSelected state to be an object, since I think it makes the management of it quite a bit easier.

Resources