In react, is it possible to manage the state of multiple checkboxes with id attribute or other attributes such as data-*?
For the moment all I'm using is the name attribute, however in my project I need to use the id or preferably data-* due to the complexity of the project, but it seems that in react it's isn't possible.
Or am I not understanding in?
import React, { useState } from 'react';
import someData from './someData'
function Checkbox({ name, id, label, checked, onChange }) {
return (
<span>
<input
type="checkbox"
name={name}
id={id}
checked={checked}
onChange={onChange}
/>
<span>{label}</span>
</span>
);
}
function App() {
const [isChecked, setIsChecked] = useState()
const onCheckboxChange = event => {
const target = event.currentTarget;
const name = target.name
const id = target.id;
const checked = target.checked;
setIsChecked({
...isChecked,
[id]: checked // using "id" seems to not work here.
})
}
return (
someData.map(item => {
return (
<Checkbox
name={item.name}
id={item.id}
label={item.name}
onChange={onCheckboxChange}
checked={isChecked}
/>
);
}
);
}
There's no problem to do what you need in React whatsoever.
You may use dataset API to access data-* attributes values to update your state on checkbox change (e.g. if you assign data-chkboxname attribute to your checkbox):
onCheckboxChange = ({target:{checked, dataset:{chkboxname}}}) => {
setIsChecked({
...isChecked,
[chkboxname]: checked
})
}
Following is a quick proof-of-a-concept live-demo:
const { useState } = React,
{ render } = ReactDOM,
rootNode = document.getElementById('root')
const App = () => {
const [isChecked, setIsChecked] = useState({}),
onCheckboxChange = ({target:{checked, dataset:{chkboxname}}}) => {
setIsChecked({
...isChecked,
[chkboxname]: checked
})
},
onFormSubmit = e => {
e.preventDefault()
console.log(isChecked)
}
return (
<form onSubmit={onFormSubmit}>
{
['chckbox1', 'chckbox2', 'chckbox3'].map(checkbox => (
<label key={checkbox}>
{checkbox}
<input
type="checkbox"
data-chkboxname={checkbox}
onChange={onCheckboxChange}
checked={isChecked[checkbox]}
/>
</label>
))
}
<input type="submit" value="submit" />
</form>
)
}
render (
<App />,
rootNode
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
Try like this
return someData.map((item) => {
return (
<Checkbox
name={item.name}
id={item.id}
label={item.name}
checked={item.checked}
/>
);
});
Related
I have React component where I can dynamically add new text inputs. So, I need to push the values from the inputs to array.
Can anyone help me how to do this?
Here is my code:
function FormPage({ setData }) {
const [item, setItem] = useState([]);
const [counter, setCounter] = useState(0);
const handleCounter = () => {
setCounter(counter + 1);
};
const addItem = (setItem) => setItem((ing) => [...ing, newItem]);
return (
{Array.from(Array(counter)).map((c, index) =>
<TextField
key={index}
label="Item"
onChange={() => setItem(i=> [...i, (this.value)])}
/>
)}
<Button onClick={handleCounter}>Add one more item</Button>
)
}
Here is example in sandbox:
https://codesandbox.io/s/solitary-sound-t2cfy?file=/src/App.js
Firstly, you are using two-way data binding with your TextField component, so you also need to pass a value prop.
Secondly, to get the current value of TextField, we don't use this.value. Rather, the callback to onChange takes an argument of type Event and you can access the current value as follows
<TextField
...
onChange={(e) => {
const value = e.target.value;
// Do something with value
}}
/>
You cannot return multiple children from a component without wrapping them by single component. You are simply returning multiple TextField components at the same level, which is also causing an error. Try wrapping them in React.Fragment as follows
...
return (
<React.Fragment>
{/* Here you can return multiple sibling components*/}
</React.Fragment>
);
You are mapping the TextField components using counter which is equal to the length of item array. In handleCounter, we'll add a placeholder string to accomodate the new TextField value.
...
const handleCounter = () => {
setCounter(prev => prev+1); // Increment the counter
setItem(prev => [...prev, ""]); // Add a new value placeholder for the newly added TextField
}
return (
<React.Fragment>
{ /* Render only when the value of counter and length of item array are the same */
counter === item.length && (Array.from(Array(counter).keys()).map((idx) => (
<TextField
key={idx}
value={item[idx]}
label="Item"
onChange={(e) => {
const val = e.target.value;
setItem(prev => {
const nprev = [...prev]
nprev[idx] = val;
return nprev;
})
}}
/>
)))}
<br />
<Button onClick={handleCounter}>Add one more item</Button>
</React.Fragment>
);
Here is the sandbox link
Try this:
import "./styles.css";
import React, { useState } from "react";
export default function App() {
// Changes made here
const [item, setItem] = useState({});
const [counter, setCounter] = useState(0);
console.log("item 1:", item[0], "item 2:", item[1],item);
const handleCounter = () => {
setCounter(counter + 1);
};
const addItem = (newItem) => setItem((ing) => [...ing, newItem]);
return (
<>
{Array.from(Array(counter)).map((c, index) => (
<input
type="text"
key={index}
//Changes made here
value={item[index]}
label="Item"
// Changes made here
onChange={(event) => setItem({...item, [index]:event.target.value })}
/>
))}
<button onClick={handleCounter}>Add one more item</button>
</>
);
}
Instead of using an array to store the input values I recommend using an object as it's more straight-forward.
If you wanted to use an array you can replace the onChange event with the following:
onChange={(event) => {
const clonedArray = item.slice()
clonedArray[index] = event.target.value
setItem(clonedArray)
}}
It's slightly more convoluted and probably slightly less optimal, hence why I recommend using an object.
If you want to loop through the object later you can just use Object.entries() like so:
[...Object.entries(item)].map(([key, value]) => {console.log(key, value)})
Here's the documentation for Object.entries().
codeSolution: https://codesandbox.io/s/snowy-cache-dlnku?file=/src/App.js
import "./styles.css";
import React, { useState } from "react";
export default function App() {
const [item, setItem] = useState(["a", "b"]);
const handleCounter = () => {
console.log(item, "item");
setItem([...item, ""]);
};
const setInput = (index) => (evt) => {
item.splice(index, 1, evt.target.value);
setItem([...item]);
};
return (
<>
{item.map((c, index) => {
return (
<input
type="text"
key={index}
label="Item"
value={c}
onChange={setInput(index)}
/>
);
})}
<button onClick={handleCounter}>Add one more item</button>
</>
);
}
I have solved for you . check if this works for you , if any issues tell me
I've created a simple star rating component to make users able to review my books.
Here's the component:
import React, { useState } from 'react'
import { FaStar } from 'react-icons/fa'
const StarRating = (props) => {
const [rating, setRating] = useState(null);
return (
<Wrapper>
{[...Array(5)].map((star, i) => {
const ratingValue = i + 0;
return (
<label>
<input
type="radio"
name="rating"
onClick={() => setRating(props.ratingValue)}
/>
<FaStar color={ratingValue < rating ? "#01af93" : "#bbb"} />
</label>
)
})}
</Wrapper>
)
}
export default StarRating
So, if somebody clicks on the Stars the rating will appear (using an onClick handler).
I would like to display the ratings without the onClick handler now.
I've tried simply to add value={props.ratingValue} instead of onClick={() => setRating(props.ratingValue)} but it doesn't work.
Hope someone can help with what I'm doing wrong.
You have to move onClick handler and value to the parent container class. So changing state and keeping current input value must be done in your parent container. Below I share a code snippet for your sample.
import React, { useState } from "react";
import { FaStar } from "react-icons/fa";
const StarRating = (props) => {
console.log(props);
return (
<div>
{Array(5)
.fill(0)
.map((_, idx) => (
<label key={idx}>
<input
type="radio"
name="rating"
onChange={() => props.setRating(idx)}
value={props.ratingValue}
checked={idx === props.ratingValue}
/>
<FaStar color={idx < 3 ? "#01af93" : "#bbb"} />
</label>
))}
</div>
);
};
export const RatingContainer = () => {
const [rate, setRate] = useState(3);
return (
<div>
<StarRating setRating={(val) => setRate(val)} ratingValue={rate} />
</div>
);
};
I want to get the state of child component when the button is clicked in the parent component.
child component handles its own states. but when an action is triggered in the parent component I want the data of a child component
the code snippet in the simplest form as this and I can't change the component architecture
const Child = (props) => {
const [name, setName] = useState("")
return (
<input value={name} onChange={(e) => { setName(e.target.value) }} />
)
}
const parent = (props) => {
const abc=()=>{
// i want the name value here
}
return (
<React.Fragment>
<Child />
<button onClick={()=>{abc()}}>Abc</button>
</React.Fragment>
)
}
As with most problems, there are multiple ways to solve this one. Each solution will be more appropriate and readable for a different use case.
One option would be to move the state into the parent component.
const Child = (props) => {
return (
<input value={props.name} onChange={(e) => { props.setName(e.target.value) }} />
)
}
const parent = (props) => {
const [name, setName] = useState("")
const abc=()=>{
// i want the name value here
}
return (
<React.Fragment>
<Child name={name} setName={setName} />
<button onClick={()=>{abc()}}>Abc</button>
</React.Fragment>
)
}
Another method would be to use useRef. More documentation on this use case
const Child = (props) => {
const [name, setName] = useState("")
props.nameRef.current = name
return (
<input value={name} onChange={(e) => { setName(e.target.value) }} />
)
}
const parent = (props) => {
const nameRef = useRef("");
const abc=()=>{
// i want the name value here
}
return (
<React.Fragment>
<Child nameRef={nameRef}/>
<button onClick={()=>{abc()}} >Abc</button>
</React.Fragment>
)
}
IMHO most of the time you should use the first one. However, if it makes the code more readable for the state to live in the child component, or it would take too much time to refactor, then the second example works as well.
const Child = (props) => {
return (
<input value={name} onChange={e => setName(e.target.value) } />
)
}
const parent = (props) => {
const [name, setName] = useState("")
const abc=()=>{
// i want the name value here
}
return (
<React.Fragment>
<Child setName={setName}/>
<button onClick={()=>{abc()}}>Abc</button>
</React.Fragment>
)
}
I am trying to check the checkbox in componentDidUpdate by setting the "checked" to true but it's not working.
My State is stored in redux, I update the checkbox state in "onChange()" and in componentDidUpdate, I check if the state value is true or false based on which I am adding and removing the "checked" attribute. but this doesn't seem to work
import React, { Component } from "react";
import { saveOptions, getOptions } from '../api'
import { connect } from 'react-redux';
import { setUploadeSettings } from '../actions/settings'
import { Button, Checkbox, FormControl, TextField, withStyles } from '#material-ui/core/';
const styles = theme => ({
root: {
padding: theme.spacing(2)
},
button: {
marginTop: theme.spacing(1)
}
})
class UploadSettings extends Component{
async componentDidMount(){
const settingsFields = [...this.form.elements].map(i => i.name).filter(i => i)
let savedOptions = await getOptions( { option_names: settingsFields } )
savedOptions = savedOptions.data.data;
const reduxState = {}
savedOptions.forEach(i => reduxState[i.option_name] = i.option_value );
this.props.setUploadeSettings(reduxState)
}
componentDidUpdate(prevProps){
if(prevProps.uploadSettings !== this.props.uploadSettings){
[...this.form.elements].forEach(i => {
if(i.name === "convert_web_res")
this.props.convert_web_res ? i.setAttribute("checked", true) : i.removeAttribute('checked') //<- This Doesn't Seem to work
i.value = this.props.uploadSettings[i.name]
})
}
}
handleSubmit = async (e) => {
e.preventDefault();
try{
const formData = new FormData(e.target);
const elements = [...e.target.elements]
const eleIndex = elements.findIndex(i => i.name === 'convert_web_res')
const checked = elements[eleIndex].checked;
formData.set('convert_web_res', checked);
await saveOptions(formData)
} catch(e){
console.error(e)
}
}
render(){
const { classes, uploadSettings, setUploadeSettings } = this.props;
return(
<div className={classes.root}>
<form onSubmit={this.handleSubmit} ref={(form) => this.form = form}>
<FormControl>
<label>
<Checkbox
color="primary"
name="convert_web_res"
onChange={(e) => {
const state = uploadSettings;
state.convert_web_res = e.target.checked
setUploadeSettings(state)
}}
/> Convert Web Resolution
</label>
<TextField
defaultValue = {uploadSettings.resolution}
id="resolution"
name="resolution"
label="Resolution"
margin="normal"
/>
<TextField
defaultValue = {uploadSettings.dpi}
id="dpi"
name="dpi"
label="DPI"
margin="normal"
/>
<Button type="submit" variant="contained" color="primary" className={classes.button}>
Save
</Button>
</FormControl>
</form>
</div>
)
}
}
const mapStateToProps = state => {
return {
uploadSettings: state.settings.upload_settings
}
}
const mapDispatchToProps = dispatch => {
return {
setUploadeSettings: settings => dispatch(setUploadeSettings(settings))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(UploadSettings));
The Goal is to set the default value of the checkbox.
I tried adding the "checked" attribute directly on my checkbox component, Like so
<label>
<Checkbox
color="primary"
name="convert_web_res"
checked={uploadSettings.convert_web_res === "true" ? true : false}
onChange={(e) => {
const state = uploadSettings;
state.convert_web_res = e.target.checked
setUploadeSettings(state)
}}
/> Convert Web Resolution
</label>
this checks the checkbox but it gets frozen, User loses the ability to toggle the checkbox,
Use value and checked.
handleChange = event => {
// fire redux action
this.props.setUploadeSettings({ convert_web_res: event.target.checked });
};
<label>
<Checkbox
color="primary"
name="convert_web_res"
value={'Convert Web Resolution'}
checked={this.props.uploadSettings.convert_web_res}
onChange={(e) => this.handleChange(e)}
/> Convert Web Resolution
</label>
I want to make my class more reusable. I have huge form to handle, and I don't want write single method for each input. How to pass state value as parameter to method?
I was trying:
state = {
subtitle: ""
};
inputHandler = (e, param) => this.setState({ [param]: e.target.value });
render() {
return (
<>
{this.state.title}
<input
type="text"
value={this.state.subtitle}
onChange={e => this.inputHandler(this.state.subtitle)}
/>
</>
);
}
and different similar combinations of this solution.
Demo: https://codesandbox.io/s/kw6pnxwv0v
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
state = {
subtitle: ""
};
onChange = (e) => {
const target = e.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<>
{this.state.title}
<input
type="text"
name="subtitle"
value={this.state.subtitle}
onChange={this.onChange}
/>
{this.state.subtitle}
</>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Let me know if this helps :)