React - SUM state values - reactjs

EDIT: I forgot to say that the onChange on the inputs doesn't work, how do I tell to onChange it's the state value inside the group?
I started to learn React few weeks ago. I want to SUM the group state values and every time it changes the total value changes too. I'm using material ui to create the form. The problem is if I change the state value to int the FormControlLabel value won't work... as a string the total doesn't work as expectable. And how I can handle the total change everytime I change the option?
Thanks in advance.
class App extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
total: 0,
group: {
feedingGroup: "3",
bathingA: "3",
bathingB: "3",
}
}
}
componentDidMount() {
this.setState({ total: this.calculateTotal(this.state.group) });
}
calculateTotal = (values) => {
return Object.entries(values).reduce((finalValue, [key, value]) => {
return finalValue + value;
}, 0);
}
handleSubmit(event) {
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<FormControl component="fieldset" error="" className="">
<RadioGroup aria-label="quiz" name="quiz" value={this.state.group.feedingGroup}
onChange={(value) => { this.setState({ feedingGroup: (value.target.value) }); }}>
<FormControlLabel value="0" control={<Radio />} label="Op1" />
<FormControlLabel value="1" control={<Radio />} label="Op2" />
<FormControlLabel value="2" control={<Radio />} label="Op3" />
<FormControlLabel value="3" control={<Radio />} label="Op4" />
</RadioGroup>
<RadioGroup aria-label="quiz" name="quiz" value={this.state.group.bathingA}
onChange={(value) => { this.setState({ bathingA: (value.target.value) }); }}>
<FormControlLabel value="0" control={<Radio />} label="Op1" />
<FormControlLabel value="1" control={<Radio />} label="Op2" />
<FormControlLabel value="2" control={<Radio />} label="Op3" />
<FormControlLabel value="3" control={<Radio />} label="Op4" />
</RadioGroup>
<Button type="submit" variant="outlined" color="primary" className="">
Check Answer
</Button>
<FormHelperText>{this.state.total}</FormHelperText>
</FormControl>
</form>
);
}
}
export default App;

How about casting the value:
calculateTotal = (values) => {
return Object.entries(values).reduce((finalValue, [key, value]) => {
return finalValue + Number(value);
}, 0);
}

Related

Removing Items from an Array When Radio Button in Form is Selected/Form Submitted

I have developed a short questionnaire in React using Material-UI as shown below. I am pulling the questions from a json file I have that looks like the following:
[ {
"id": 1,
"question": "Which section 1 criterion are applicable?"
"criterion": ["1.1", "1.2", "1.3"]
"decision": "Yes"
},
{
"id": 2,
"question": "Which section 2 criterion are applicable?"
"criterion": ["2.1", "2.2", "2.3"]
"decision": "No"
},
...
]
At the start of the questionnaire, all criteria are added to applicableCriteria (shown below). Based on the user's responses, I am trying to remove criterion from applicableCriteria. For example, if the user responds 'Yes' to question 1, the list of criterion shown for the question with id=1 should be removed from applicableCriteria. I have experimented with some different things, but my novice JavaScript skills haven't gotten me far. Below is a working, error-free version of what I've been working with. Any tips on how I should go about this successfully?
import * as React from "react";
import TextField from "#mui/material/TextField";
import {
Button,
Radio,
RadioGroup,
FormControlLabel,
FormControl,
FormLabel,
FormHelperText,
FormGroup,
Checkbox,
Grid,
Box,
} from "#mui/material";
import { useForm } from "react-hook-form";
import Records from "./CriterionQuestions.json";
const QuestionnaireForm = () => {
const {
register,
handleSubmit,
watch,
formState: { errors },
} = useForm();
const onSubmit = (data) => console.log(data);
console.log(errors);
let applicableCriteria = [];
// Push all possible criteria to list
for (let q = 0; q < Records.length; q++) {
for (let c = 0; c < Records[q].criterion.length; c++) {
applicableCriteria.push(Records[q].criterion[c]);
}
}
return (
<div className="App__form">
<h1> Questionnaire </h1>
<h2>{applicableCriteria}</h2>
<form onSubmit={handleSubmit(onSubmit)}>
{/* Radio button */}
<FormControl error={Boolean(errors.question1)}>
<FormLabel component="legend">
{/* {" "}
Is the modification applicable to only fixed-wing and non-commercial
derivative aircraft?{" "} */}
{Records[0].question}
</FormLabel>
<RadioGroup row aria-label="question1" name="question1">
<FormControlLabel
value="yes"
control={
<Radio
{...register("question1", {
required: "Please select a response.",
})}
/>
}
label="Yes"
/>
<FormControlLabel
value="no"
control={
<Radio
{...register("question1", {
required: "Please select a response.",
})}
/>
}
label="No"
/>
</RadioGroup>
<FormHelperText style={{ color: "#d32f2f" }}>
{errors.question1?.message}
</FormHelperText>
</FormControl>
<FormControl error={Boolean(errors.question2)}>
<FormLabel component="legend">{Records[1].question}</FormLabel>
<RadioGroup row aria-label="question2" name="question2">
<FormControlLabel
value="yes"
control={
<Radio
{...register("question2", {
required: "Please select a response.",
})}
/>
}
label="Yes"
/>
<FormControlLabel
value="no"
control={
<Radio
{...register("question2", {
required: "Please select a response.",
})}
/>
}
label="No"
/>
</RadioGroup>
<FormHelperText style={{ color: "#d32f2f" }}>
{errors.question2?.message}
</FormHelperText>
</FormControl>
<div className="clearfix"></div>
<FormControl error={Boolean(errors.question8)}>
<FormLabel component="legend">{Records[7].question}</FormLabel>
<RadioGroup row aria-label="question8" name="question8">
<FormControlLabel
value="yes"
control={
<Radio
{...register("question8", {
required: "Please select a response.",
})}
/>
}
label="Yes"
/>
<FormControlLabel
value="no"
control={
<Radio
{...register("question8", {
required: "Please select a response.",
})}
/>
}
label="No"
/>
</RadioGroup>
<FormHelperText style={{ color: "#d32f2f" }}>
{errors.question8?.message}
</FormHelperText>
</FormControl>
<div className="clearfix"></div>
<FormControl error={Boolean(errors.question9)}>
<FormLabel component="legend">{Records[8].question}</FormLabel>
<RadioGroup row aria-label="question9" name="question9">
<FormControlLabel
value="yes"
control={
<Radio
{...register("question9", {
required: "Please select a response.",
})}
/>
}
label="Yes"
/>
<FormControlLabel
value="no"
control={
<Radio
{...register("question9", {
required: "Please select a response.",
})}
/>
}
label="No"
/>
</RadioGroup>
<FormHelperText style={{ color: "#d32f2f" }}>
{errors.question9?.message}
</FormHelperText>
</FormControl>
<div className="clearfix"></div>
<TextField
id="outlined-basic"
name="modName"
label="Modification Name"
variant="outlined"
fullWidth
{...register("modName", {
required: "Modification Name is required.",
})}
error={Boolean(errors.modName)}
helperText={errors.modName?.message}
/>
{/* Check box */}
<Box sx={{ paddingTop: 3 }} />
<div className="clearfix"></div>
<Button
variant="contained"
color="primary"
type="submit"
className="btns"
>
Submit Responses
</Button>
</form>
</div>
);
};
export default QuestionnaireForm;

React js show/hide specific element using Material UI checkbox's state

hello I'm trying to show and hide specific elements with checkbox material and what is happening now is when one checkbox is checked all the hidden divs are showing up.
You can see the problem here: https://stackblitz.com/edit/react-1ecdqb?file=demo.tsx
edit: I know that I need more variables in the state but I ask if there is a way to do it without state for each checkbox because there are gonna be 10 more checkboxes
const UninstallView = () => {
const [isChecked, setIsChecked] = useState(false);
const handleChange = event => {
if (event.target.checked) {
setIsChecked(true);
}
else {
setIsChecked(false);
}
}
return (
<div>
<FormGroup>
<FormControlLabel control={<Checkbox onChange={handleChange} />} label="simple isn't what I expected" />
{isChecked ? <TextField
id="filled-multiline-static"
label="What did you expect from simple?"
multiline
rows={4}
defaultValue=""
variant="filled"
/>
: '' }
</FormGroup>
<FormGroup>
<FormControlLabel control={<Checkbox onChange={handleChange} />} label="simple isn't working correctly" />
{isChecked ?
<div>
<h1>hello</h1>
</div>
: '' }
</FormGroup>
</div>
);
You are sharing 1 state across 2 checkboxes. You should have a separate state that holds the state for each checkbox.
This code may help:
const UninstallView = () => {
const [isFirstChecked, setIsFirstChecked] = useState(false);
const [isSecondChecked, setIsSecondChecked] = useState(false);
return (<div>
<FormGroup>
<FormControlLabel
control={<Checkbox onChange={() => setIsFirstChecked(!isFirstChecked)}/>}
label="simple isn't what I expected"/>
{isFirstChecked ? <TextField
id="filled-multiline-static"
label="What did you expect from simple?"
multiline
rows={4}
defaultValue=""
variant="filled"
/> : ''}
</FormGroup>
<FormGroup>
<FormControlLabel
control={<Checkbox onChange={() => setIsSecondChecked(!isSecondChecked)}/>}
label="simple isn't working correctly"/>
{isSecondChecked ? <div>
<h1>hello</h1>
</div> : ''}
</FormGroup>
</div>);
}
Sandbox example

RHF not resetting values in an MUI checkbox group

I am trying to reset my form with the reset() method and, it is working partially. All of the form's fields get reset, except for the checkboxes group.
I have tried three different ways of implementing an MUI CheckboxesGroup but, I always get the same result.
You can see a working example in this sandbox
Checkbox one implementation
<FormControl error={error} required={required} component="fieldset">
<FormLabel component="legend">{label}</FormLabel>
<Controller
name={name}
render={({ field: { onBlur: rhfonBlur, onChange: rhfonChange } }) => (
<FormGroup onBlur={rhfonBlur}>
{Object.keys(options).map((key) => {
return (
<FormControlLabel
label={key}
key={key}
control={
<Checkbox onChange={(e) => rhfonChange(handleCheck(key))} />
}
/>
);
})}
</FormGroup>
)}
control={control}
/>
<FormHelperText>{helperText}</FormHelperText>
</FormControl>
Checkbox two implementation
<FormControl error={error}>
<FormLabel component="legend">{label}</FormLabel>
<FormGroup>
{Object.keys(options).map((key) => {
return (
<FormControlLabel
key={key}
control={<Checkbox {...register(name)} />}
value={key}
label={key}
/>
);
})}
</FormGroup>
<FormHelperText>{helperText}</FormHelperText>
</FormControl>
Checkbox three implementation
<FormControl required error={error} component="fieldset" variant="standard">
<FormLabel component="legend">{label}</FormLabel>
<Controller
control={control}
name={name}
render={({ field: { onChange: rfhonChange, value } }) => {
// console.log("value:", value);
return (
<FormGroup>
{Object.keys(state).map((key) => {
return (
<FormControlLabel
label={key}
key={key}
control={
<Checkbox
onChange={(e) => {
rfhonChange(handleChange(e));
}}
checked={state[key]}
name={key}
/>
}
/>
);
})}
</FormGroup>
);
}}
/>
<FormHelperText>{helperText}</FormHelperText>
</FormControl>
Your third implementation was the correct way of doing it by using RHF's <Controller /> component. The reason why it wasn't working was because of the following:
you're not updating RHF's internal state for your field as you are only passing a function to the onChange handler. There is no need to use an additional useState here
your initial value is a boolean (false) but you are using an object inside the <CheckboxThree /> component
So basically you can simplify the component to the following (in you're example it isn't clear if the field value should be an object or an array containing the selected options - so the example i made is using an array):
const CheckboxThree = ({
control,
error,
helperText,
label,
name,
options
}) => {
return (
<FormControl required error={error} component="fieldset" variant="standard">
<FormLabel component="legend">{label}</FormLabel>
<Controller
control={control}
name={name}
render={({ field: { onChange, value, ref, ...field } }) => (
<FormGroup>
{Object.keys(options).map((key) => {
return (
<FormControlLabel
label={key}
key={key}
control={
<Checkbox
{...field}
name={key}
checked={value.some((option) => option === key)}
onChange={(event, checked) => {
if (checked) {
onChange([...value, event.target.name]);
} else {
onChange(
value.filter((value) => value !== event.target.name)
);
}
}}
inputRef={ref}
/>
}
/>
);
})}
</FormGroup>
)}
/>
<FormHelperText>{helperText}</FormHelperText>
</FormControl>
);
};

React Function loading initial value for Radio Group and selecting it

I am having an issue with setting the initial value of a radio group (from a value passed into the function) like this:
export default function SportTeamRanking({team, ...props}) {
So right now I have a Radio Group with 4 radio buttons and the value I want to pre-select or check is contained in the team object specified above (something like team.ranking). This value is passed from a class component into this function. So let's say the team object had a team.ranking value of "rank2" so ideally the Rank 2 radio button would be pre-selected when the function loads.
I have an initial state:
const [value, setValue] = React.useState("");
I've tried setting the React.useState(team.ranking) but still no button is selected.
I also tried using the useEffect and run it only once:
useEffect(() => {
setValue(team.ranking)
}, []);
but with no luck. I tried console logging early on and seems like the team object is still empty while all of this code is already executed?
The goal of this function is to pre-load any saved value that might have been set before (from the team object passed in), then update to a new ranking value if required and save.
For extra info here is the radio group code in the return body:
<FormControl component="fieldset">
<FormLabel component="legend">Team Ranking</FormLabel>
<RadioGroup aria-label="ranking" name="ranking1" value={value} onChange={handleChange}>
<FormControlLabel value="rank1" control={<Radio />} label="Rank 1" />
<FormControlLabel value="rank2" control={<Radio />} label="Rank 2" />
<FormControlLabel value="rank3" control={<Radio />} label="Rank 3" />
<FormControlLabel value="rank4" control={<Radio />} label="Rank 4" />
</RadioGroup>
</FormControl>
And the onChange is simply:
const handleChange = (event) => {
setValue(event.target.value);
};
Any suggestions are appreciated, thanks!
As you have mentioned that the value is passed from a class component to a function component, see if the below code answers your question.
function FormControlLabelPosition(props) {
const onChange = (e) => props.onChange(e.target.value)
return (
<FormControl component="fieldset">
<FormLabel component="legend">Team Ranking</FormLabel>
<RadioGroup aria-label="ranking" name="ranking1" value={props.value} onChange={onChange}>
<FormControlLabel value="rank1" control={<Radio />} label="Rank 1" />
<FormControlLabel value="rank2" control={<Radio />} label="Rank 2" />
<FormControlLabel value="rank3" control={<Radio />} label="Rank 3" />
<FormControlLabel value="rank4" control={<Radio />} label="Rank 4" />
</RadioGroup>
</FormControl>
);
}
export default class Parent extends React.Component {
state = {
value: "rank4"
}
handleChange = (val:string) => this.setState({value: val})
render() {
return (<FormControlLabelPosition value={this.state.value} onChange={this.handleChange}/>)
}
}
Try it out here: https://codesandbox.io/s/material-demo-4ilxb

Putting a customised radio button component inside a Radio Group in Material UI

I want to have a list of radio buttons, with one option being a freestyle 'Other' text box that lets the user enter their own text.
Here I have a working sandbox of everything I want to do:
https://codesandbox.io/s/r4oo5q8q5o
handleChange = event => {
this.setState({
value: event.target.value
});
};
selectItem = item => {
this.setState({
selectedItem: item
});
};
handleOtherChange = event => {
this.setState({
otherText: event.target.value
});
this.selectItem(
//Todo put in right format
this.state.otherText
);
};
focusOther = () => {
this.setState({
value: "Other"
});
this.selectItem(this.state.otherText);
};
render() {
const { classes, items } = this.props;
const { value } = this.state;
return (
<div className={classes.root}>
<Typography>
{" "}
Selected item is: {JSON.stringify(this.state.selectedItem)}
</Typography>
<FormControl component="fieldset" fullWidth>
<RadioGroup value={this.state.value} onChange={this.handleChange}>
{items.map(v => (
<FormControlLabel
value={v.name}
control={<Radio />}
label={v.name}
key={v.name}
onChange={() => this.selectItem(v)}
/>
))}
<FormControlLabel
value="Other"
control={<Radio />}
label={
<TextField
placeholder="other"
onChange={this.handleOtherChange}
onFocus={this.focusOther}
/>
}
onChange={() => this.selectItem(this.state.otherText)}
/>
</RadioGroup>
</FormControl>
</div>
);
}
}
Now what I want to do is make the 'Other' text box its own component.
Here's my attempt:
https://codesandbox.io/s/ryomnpw1o
export default class OtherRadioButton extends React.Component {
constructor() {
super();
this.state = {
text: null
};
}
handleTextChange = event => {
this.setState({
text: event.target.value
});
this.props.onChange(this.state.text);
};
focusOther = () => {
this.props.onFocus(this.props.value);
this.props.onChange(this.state.text);
};
render() {
return (
<FormControlLabel
value={this.props.value}
control={<Radio />}
label={
<TextField
placeholder="other"
onChange={this.handleTextChange}
onFocus={this.focusOther}
/>
}
onChange={this.focusOther}
/>
);
}
}
Used with:
<OtherRadioButton
value="Other"
onFocus={v => this.setState({ value: v})}
onChange={v => this.selectItem(v)}
/>
As you can see - the value of the free text is propagating back fine - but the RadioGroup seems like it's not aware of the FormGroupLabel's value.
Why is this, and how would I solve this?
You can check the RadioGroup source code here.
And I have written my own code to better illustrate how it can be fixed. See here: https://codesandbox.io/s/mz1wn4n33j
RadioGroup creates some props to its FormControlLabel/RadioButton children. By creating your own customized radio button in a different component, these props are not passed to FormControlLabel/RadioButton.
You can fix these by passing the props to your FormControlLabel in your custom RadioButton.
<FormControlLabel
value={this.props.value} //Pass this
onChange={this.props.onChange} //Pass this one too
checked={this.props.checked} //Also this
control={<Radio name="gender" />}
label={
<TextField
id="standard-bare"
defaultValue={this.props.defaultValue}
margin="normal"
onChange={this.props.onTextChange}
/>
}
/>

Resources