React newbie here.
I'm trying to make a multi-step form to get some details from the user and for some of the nested views, there are only radio buttons. The handleChange works fine on text type but how can I handle radio buttons?
React does not render the radio button that is used to render in AngularJS, so I am a bit confused. I can choose multiple radio button and all of them looks like checkboxes instead of choosing only one
MainForms.js
export class UserForm extends Component {
state = {
step: 1,
tema: '',
area: '',
type: '',
name: ''
};
// Proceed to next step
nextStep = () => {
const { step } = this.state;
this.setState({
step: step + 1
});
};
// Go back to prev step
prevStep = () => {
const { step } = this.state;
this.setState({
step: step - 1
});
};
// Handle fields change
handleChange = input => e => {
this.setState({ [input]: e.target.value });
console.log(e.target.value)
};
render() {
const { step } = this.state;
const { area, name, type, tema} = this.state;
const values = { area, name, type, tema};
switch (step) {
case 1:
return (
<FormUserDetails
nextStep={this.nextStep}
handleChange={this.handleChange}
values={values}
/>
);
FormUserDetails.js
export class FormUserDetails extends Component {
continue = (e) => {
e.preventDefault();
this.props.nextStep();
};
back = (e) => {
e.preventDefault();
this.props.prevStep();
}
render() {
const { handleChange } = this.props;
return (
<div className="box item2">
<div className="radio">
<label>
<input
type="radio"
onChange={handleChange}
/>
Basic
</label>
</div>
);
}
}
form example
For the radio input not to "work like an checkbox" they all need the same name in this case its "yyy".
<div className="box item2">
<div className="radio">
<label>
<input
type="radio"
name="yyy"
value="123"
onChange={handleChange}
/>
Basic
</label>
</div>
<div className="radio">
<label>
<input
type="radio"
name="yyy"
value="456"
onChange={handleChange}
/>
Basic
</label>
</div>
</div>
Your handleChange receives the event and not some input prop. To see if the radio input is checked or not you can use the checked property.
handleChange = (e) => {
const target = e.target;
// do whatever you want with the state :)
this.setState({ [target.name]: target.value });
};
I'm not 100% sure if I understand your question correct and if my answer helps you, but what I have made in my solution is something like this:
export const RadioButtonInputField = ({label, onChangeOut}) => {
const [state, setState] = useState(true);
const setOnChange = (value) => {
onChangeOut({value: value});
setState(value);
};
return (
<div className={`dynamic-input-container`}>
<div className={'radio-button-wrapper'}>
<p className={'radio-title'}>{label}</p>
<label className={'radio-label first-radio-label'}>
<input
type='radio'
value={true}
checked={state === true}
className={'radio-input'}
onChange={() => {
setOnChange(true);
}}
/>
<span className={'radio-btn-label'}>Yes</span>
</label>
<br />
<label className={'radio-label'}>
<input
type='radio'
value={false}
checked={state === false}
className={'radio-input'}
onChange={() => {
setOnChange(false);
}}
/>
<span className={'radio-btn-label'}>No</span>
</label>
</div>
</div>
);
}
Related
I am trying to build a quiz application where I want to generate no of Question input fields based on admin inputs.
So suppose the admin enters 10 questions for the quiz.
Then I am rendering the form inside for loop for 10 Questions and their answers respectively.
The problem I am facing is I am not able to get all values from input fields.
Below is my demo code:
import { useState } from "react";
const MyComponent = () => {
const [inputs, setInputs] = useState({});
const handleChange = (e) =>
setInputs((prevState) => ({
...prevState,
[e.target.name]: e.target.value
}));
const finalData = (e) => {
e.preventDefault();
console.log("data", inputs);
};
function buildRows() {
const arr = [];
for (let i = 1; i <= 3; i++) {
arr.push(
<div key={i} id={i}>
<input name="Question" onChange={handleChange} />
<input name="option1" onChange={handleChange} />
<input name="option2" onChange={handleChange} />
<input name="option3" onChange={handleChange} />
<input name="option4" onChange={handleChange} />
</div>
);
}
return arr;
}
return (
<>
{buildRows()}
<button
onClick={(e) => finalData(e)}
variant="contained"
className="button-left"
sx={{ marginRight: 3.5 }}
>
Submit Quiz Questions
</button>
</>
);
};
export default MyComponent;
You could use the id (or any other unique property, a unique name would probably be preferred) you're giving your div and build your object with that as an array index like so:
const handleChange = (e) => {
const parent = e.currentTarget.parentNode;
const id = parent.id;
setInputs((prevState) => ({
...prevState,
[id]: {
...prevState[id],
[e.target.name]: e.target.value
}
}));
};
This produces an object like this:
{
"1":{
"Question":"1",
"option1":"2",
"option2":"3",
"option3":"4",
"option4":"5"
},
"2":{
"Question":"6",
"option1":"7",
"option2":"8",
"option3":"9",
"option4":"11"
},
"3":{
"Question":"22",
"option1":"33",
"option2":"44",
"option3":"55",
"option4":"66"
}
}
I am displaying a list. Each item in the list is having a textbox. Textbox is showing DisplayOrder. Please find the sandbox: https://codesandbox.io/s/solitary-butterfly-4tg2w0
In Post API call, how to pass changed textbox values with corresponding description-id as a collection. StoredProcedure is taking description-id and display-sequence as parameters to save changed data in the database.
Please help on form submit how to do this? Thanks
import "./styles.css";
import React from "react";
import XMLParser from "react-xml-parser";
const data = `<?xml version="1.0"?>
<Category>
<description description-id="11" display-sequence="2">testing</description>
<description description-id="15" display-sequence="5">Guide</description>
<description description-id="20" display-sequence="7">test</description>
<description description-id="25" display-sequence="10">Guide</description>
<description description-id="30" display-sequence="12">test</description>
</Category>
</xml>`;
const REQUEST_URL = "";
const axios = {
get: () =>
new Promise((resolve) => {
setTimeout(resolve, 1000, { data });
})
};
class Sort_Descr extends React.Component {
constructor(props) {
super(props);
this.state = {
proddescriptions: [],
proddescription_id: "",
display_sequence: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {}
componentDidMount() {
this.getlistofdescriptions();
}
getlistofdescriptions() {
axios
.get(REQUEST_URL, { "Content-Type": "application/xml; charset=utf-8" })
.then((response) => {
const jsonDataFromXml = new XMLParser().parseFromString(data);
const descriptions = jsonDataFromXml.getElementsByTagName(
"description"
);
console.log(descriptions);
this.setState({
proddescriptions: jsonDataFromXml.getElementsByTagName("description")
});
});
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<div>
<ul style={{ listStyle: "none" }}>
{this.state.proddescriptions.map((item, index) => {
return (
<li key={item.attributes["description-id"]}>
{item.attributes["description-id"]}
<input
type="text"
size="5"
maxlength="3"
value={item.attributes["display-sequence"]}
onChange={this.handleChange}
/>
{item.value}
</li>
);
})}
</ul>
</div>
<input type="submit" name="submit" value="Submit" id="btnsubmit" />
</form>
</div>
);
}
}
export default function App() {
return (
<div className="App">
<h4>Sort list by updating the number in the textbox</h4>
<Sort_Descr />
</div>
);
}
Instead of storing and updating JSON/XML data in state I'd suggest mapping it to a simpler object that is easier to identify and update. React state should be the minimal amount of data necessary to represent your information, store only what you need.
const proddescriptions = descriptions.map(({ attributes, value }) => ({
id: attributes["description-id"],
sequence: attributes["display-sequence"],
value
}));
this.setState({ proddescriptions });
When mapping the state.proddescriptions to the inputs, use the id as the inputs name attribute value for identification when updating.
{this.state.proddescriptions.map((item) => (
<li key={item.id}>
<label>
{item.id}
<input
type="text"
name={item.id} // <-- name attribute
size="5"
maxLength="3"
value={item.sequence}
onChange={this.handleChange}
/>
</label>
{item.value}
</li>
))}
Implement the handleChange to shallow copy the previous state and update the matching array element by id.
handleChange = (event) => {
const { name, value } = event.target;
this.setState((prevState) => ({
proddescriptions: prevState.proddescriptions.map((el) =>
el.id === name
? {
...el,
sequence: value
}
: el
)
}));
};
In the handleSubmit callback use the onSubmit event object to prevent the default form action and access the current state.proddescriptions and map it back to any format your APIs are expecting.
handleSubmit = (event) => {
event.preventDefault();
console.log(this.state.proddescriptions);
// manipulate `state.proddescriptions` into a request payload
};
I am having a simple form that has firstName and lastName.
<label htmlFor="firstName">First Name: </label>
<input
type="text"
className="form-control"
id="firstName"
name="firstName"
value={basicDetails.firstName}
onChange={(event) => handleInputChange(event)}
/>
<label htmlFor="lastName">Last Name: </label>
<input
type="text"
className="form-control"
id="lastName"
name="lastName"
value={basicDetails.lastName}
onChange={(event) => handleInputChange(event)}
/>
For this I am trying to add validation.
The validation rules are,
Both fields should accept only text
First name is required and should have at least 4 characters.
If Last name field has value, then it needs to be at least 3 characters.
Things I have tried to achieve this,
components/utils.js
export function isLettersOnly(string) {
return /^[a-zA-Z]+$/.test(string);
}
components/basic_details.js
const handleInputChange = (event) => {
const { name, value } = event.target;
if (!isLettersOnly(value)) {
return;
}
setValue((prev) => {
const basicDetails = { ...prev.basicDetails, [name]: value };
return { ...prev, basicDetails };
});
};
On handle input field, I am making the validation to check whether the input has value but I am unable to get the point how to catch the actual validation error and display below respective input box.
Kindly please help me to display the validation message on the respective fields.
Working example:
I suggest adding an errors property to the form data in form_context:
const [formValue, setFormValue] = useState({
basicDetails: {
firstName: '',
lastName: '',
profileSummary: '',
errors: {},
},
...
});
Add the validation to basic_details subform:
const ErrorText = ({ children }) => (
<div style={{ color: 'red' }}>{children}</div>
);
const BasicDetails = () => {
const [value, setValue] = React.useContext(FormContext);
const { basicDetails } = value;
const handleInputChange = (event) => {
const { name, value } = event.target;
if (!isLettersOnly(value)) {
setValue((value) => ({
...value,
basicDetails: {
...value.basicDetails,
errors: {
...value.basicDetails.errors,
[name]: 'Can have only letters.',
},
},
}));
return;
}
switch (name) {
case 'firstName': {
const error = value.length < 4 ? 'Length must be at least 4.' : null;
setValue((value) => ({
...value,
basicDetails: {
...value.basicDetails,
errors: {
...value.basicDetails.errors,
[name]: error,
},
},
}));
break;
}
case 'lastName': {
const error = value.length < 3 ? 'Length must be at least 3.' : null;
setValue((value) => ({
...value,
basicDetails: {
...value.basicDetails,
errors: {
...value.basicDetails.errors,
[name]: error,
},
},
}));
break;
}
default:
// ignore
}
setValue((prev) => {
const basicDetails = { ...prev.basicDetails, [name]: value };
return { ...prev, basicDetails };
});
};
return (
<>
<br />
<br />
<div className="form-group col-sm-6">
<label htmlFor="firstName">First Name: </label>
<input
type="text"
className="form-control"
id="firstName"
name="firstName"
value={basicDetails.firstName}
onChange={(event) => handleInputChange(event)}
/>
</div>
<br />
{basicDetails.errors.firstName && (
<ErrorText>{basicDetails.errors.firstName}</ErrorText>
)}
<br />
<br />
<div className="form-group col-sm-4">
<label htmlFor="lastName">Last Name: </label>
<input
type="text"
className="form-control"
id="lastName"
name="lastName"
value={basicDetails.lastName}
onChange={(event) => handleInputChange(event)}
/>
</div>
<br />
{basicDetails.errors.lastName && (
<ErrorText>{basicDetails.errors.lastName}</ErrorText>
)}
<br />
</>
);
};
Lastly, check the field values and errors to set the disabled attribute on the next button in index.js. The first !(value.basicDetails.firstName && value.basicDetails.lastName) condition handles the initial/empty values state while the second condition handles the error values.
{currentPage === 1 && (
<>
<BasicDetails />
<button
disabled={
!(
value.basicDetails.firstName && value.basicDetails.lastName
) ||
Object.values(value.basicDetails.errors).filter(Boolean).length
}
onClick={next}
>
Next
</button>
</>
)}
This pattern can be repeated for the following steps.
First you must be getting converting controlled component to uncontrolled component error in your console. For controlled component it is always preferred to use state to set value for the input. And with onChange handler you set the state. I will try to put into a single component so you would get the idea and apply your case
import React, {useState} from 'react';
import {isLettersOnly} from './components/utils'; // not sure where it is in your folder structure
const MyInputComponent = ({value, ...props}) => {
const [inputValue, setInputValue] = useState(value || ''); // input value should be empty string or undefined. null will not be accepted.
const [error, setError] = useState(null);
const handleChange = event => {
const { name, value } = event.target;
if (!isLettersOnly(value)) {
setError('Invalid Input');
}
setInputValue(value);
}
return (
<>
<input
value={inputValue}
onChange={handleChange}
{...props}
/>
{error && (
<span className={"error"}>{error}</span>
)}
</>
)
}
export default MyInputComponent;
This is a very rudimentary component. just to show the concept. You can then import this component as your input field and pass necessary props like name, className etc from parent.
import React from 'react';
import MyInputComponent from 'components/MyInputComponent';
const MyForm = (props) => {
return props.data && props.data.map(data=> (
<MyInputComponent
name="lastName"
className="form-control"
value={data.lastName}
));
}
I have a list in React where I'm using .map to render a loop from an array of elements.
The radio inputs are working perfectly, everyone is independent of the others, but I can't do the same for Select.
The Select area is changing in every field, I want it to change to it specified field like I did for the options.
I tried to re-use the same handleChange that I used in the radios for it and instead I had this error.
Cannot set property 'animation' of undefined
In this line
newAnimations[indexurl].animation = name;
How do I manage to separate the selects ?
Note: This code is working fine, but it's updating the value on every field.
const onChange = (animations) => {
setAnimations(animations);
console.log(`Animation selected:`, animations);
};
Code :
https://codesandbox.io/s/dank-violet-jibzh?file=/src/App.js:1065-1194
import React, { useState } from "react";
import Select from "react-select";
export default () => {
const animationsList = [
{ value: "animation0", label: "Dance" },
{ value: "animation1", label: "Flip" },
{ value: "animation2", label: "Salsa" }
];
const reactList = [
{
id: "14444",
selected: "layout3",
animation: "animation1"
},
{
id: "24444",
selected: "layout3",
animation: "animation2"
},
{
id: "34444",
selected: "layout3",
animation: "animation1"
}
];
const [links, setLinks] = useState(reactList);
const [animations, setAnimations] = useState(animationsList[0]);
const handleChangeSelectedReact = (indexurl, layout) => {
const cloneLinks = [...links];
cloneLinks[indexurl].selected = layout;
setLinks(cloneLinks);
console.log(cloneLinks);
};
/* const onChange = (animations) => {
setAnimations(animations);
console.log(`Animation selected:`, animations);
};*/
const onChange = (indexurl, name) => {
const newAnimations = [...links];
newAnimations[indexurl].animation = name;
setAnimations(newAnimations);
console.log(newAnimations);
};
return (
<div>
<ul>
<div>
{links.map((url, indexurl) => (
<li key={url.id}>
<div>
<Select
options={animationsList}
onChange={onChange}
value={animations}
/>
<p />{" "}
<input
type="radio"
id={url.id}
name={url.id}
value="layout1"
checked={url.selected === "layout1"}
onChange={() =>
handleChangeSelectedReact(indexurl, "layout1")
}
/>
<label for="huey">Option 1</label>
</div>
<div>
<input
type="radio"
id={url.id}
name={url.id}
value="layout2"
checked={url.selected === "layout2"}
onChange={() =>
handleChangeSelectedReact(indexurl, "layout2")
}
/>
<label for="dewey">Option 2</label>
</div>
<div>
<input
type="radio"
id={url.id}
name={url.id}
value="layout3"
checked={url.selected === "layout3"}
onChange={() =>
handleChangeSelectedReact(indexurl, "layout3")
}
/>
<label for="louie">Option 3</label>
</div>
<br />
</li>
))}
</div>
</ul>
</div>
);
};
Multiple Selects share the same state, resulting in updates the value on every field. So we need to wrap Select into components and maintain their own state independently:
function SelectItem({ list }) {
const [value, setValue] = React.useState(list[0]);
const onChange = (newValue) => {
setValue(newValue);
};
return <Select options={list} onChange={onChange} value={value} />;
}
...
...
return (
<div>
{links.map((url, indexurl) => (
<SelectItem list={animationsList} />
))}
</div>
);
...
...
Update
Modify the links when select is selected:
function SelectItem(props) {
const [value, setValue] = React.useState(props.list[0]);
return (
<Select
options={props.list}
onChange={(newValue) => {
setValue(newValue);
props.onChange(newValue.value);
}}
value={value}
/>
);
}
...
...
const onChange = (index, animation) => {
const cloneLinks = [...links];
cloneLinks[index].animation = animation;
setLinks(cloneLinks);
console.log(cloneLinks);
};
return (
<div>
{links.map((url, indexurl) => (
<SelectItem
onChange={(animation) => onChange(indexurl, animation)}
list={animationsList}
/>
))}
</div>
);
...
...
Full code: https://codesandbox.io/s/happy-cloud-m6z99?file=/src/App.js:77-298
I am having hardtime by fixing this component. I got error when I am selecting multiple item with checkbox. But in my other component, there is no error, same code. Please check my code below, thank you guys.
export default class TreeNode extends React.Component{
constructor(props){
super(props)
this.state = {
collapsed: true,
checked: []
}
}
onClick(){
this.setState({
collapsed : !this.state.collapsed
});
}
checkBoxHandle(id,e){
if (e.target.checked){
let {checked} = this.state
checked.push(id)
this.setState({checked :checked })
} else{
//If checkbox uncheck = remove in the list
let {checked} = this.state
const getIndex = checked.indexOf(id)
checked.splice(getIndex,1)
this.setState({checked : checked})
}
}
render(){
let subtree = null;
if (this.props.data.children){
subtree = this.props.data.children.map(function(child){
return <TreeNode key= {child.id} data ={child} />;
}.bind(this))
}
const temp_id = this.props.data.id
var arrowClassName = 'tree-node-arrow';
var containerClassName = 'tree-node-children'
if (subtree){
return (
<div className="tree-node">
<input type="checkbox" onClick ={this.checkBoxHandle.bind(this,this.props.data.id)}/>
<a data-id={this.props.data.id}>
{this.props.data.description}
</a>
<div className={containerClassName}>
{subtree}
</div>
</div>
);
}
else {
return (
<div className="tree-node-leaf">
<input type="checkbox" onClick ={this.checkBoxHandle.bind(this,this.props.data.id)}/>
<a data-id={this.props.data.id}>
{this.props.data.description}
</a>
</div>
);
}
}
}
I am updating the checked state everytime there is checked item, and I am removing if the user unchecked the checkbox.
It's because you use the mutable methods for checked like push and splice while a component's state is immutable. So do it in an immutable way with rest operator and filter method like this:
class Checkboxes extends React.Component {
state = {
checked: []
};
onChange = (e) => {
const { checked } = this.state;
const { id } = e.target;
if (checked.indexOf(id) === -1) {
this.setState({
checked: [...checked, id]
});
} else {
this.setState({ checked: checked.filter(checkedId => checkedId !== id) });
}
}
render() {
const { checked } = this.state;
console.log(`CHECKED: ${checked}`);
return (
<div className="checkboxes">
<label htmlFor="checkbox1">Checkbox 1</label>
<input type="checkbox" onChange={this.onChange} id="1" checked={checked.indexOf("1") !== -1} />
<br />
<label htmlFor="checkbox2">Checkbox 2</label>
<input type="checkbox" onChange={this.onChange} id="2" checked={checked.indexOf("2") !== -1} />
<br />
<label htmlFor="checkbox3">Checkbox 3</label>
<input type="checkbox" onChange={this.onChange} id="3" checked={checked.indexOf("3") !== -1} />
</div>
);
}
}
Here is the working Codepen example.