I am working with React and React Forms and i am having trouble trying to handle the state changes for radio buttons. I have a function getField which dynamically renders the type of input from the state. I have another function HandleFormStateChange which handles the change event for the inputs but i am running into an issue with the radio buttons. The user should be able to select only one option at a time but it seems to be selecting simultaneous options.
Please check out this CodeSandbox.
This is the complete code:
import React from "react";
class App extends React.Component {
state = {
Forms: [{ name: "Radio", type: "radio", options: ["a", "b", "c"] }]
};
handleFormStateChange = (event, idx) => {
const target = event.target;
const form = [...this.state.Forms];
form[idx].value = "";
form[idx].value = target.type === "radio" ? target.value : form[idx].value;
this.setState({
form
});
};
getField = (field, index) => {
switch (field.type) {
case "radio":
return (
<div>
{field.options.map(option => (
<label key={field.type + "op" + option}>
{option}:
<input
onChange={event => {
this.handleFormStateChange(event, index);
}}
key={option}
type={field.type}
name={option}
value={option}
/>
</label>
))}
</div>
);
default:
return <div>Unknown form field</div>;
}
};
renderForm = () => {
return this.state.Forms.map((field, index) => (
<label key={index}>
{field.name}
{this.getField(field, index)}
</label>
));
};
render() {
return <div>{this.renderForm()}</div>;
}
}
export default App;
Any help will be appreciated. Thank you :)
It is selecting multiple options because your radio buttons are not grouped by their name, they have separate names of their own. I've made an edit in your codesandbox - we have to have same name for your radio buttons to group them together. The same is the case with checkboxes if you want to group them together.
I've made couple of changes to your code:
I've added a name to identify the radio-group.
state = {
Forms: [
{
name: "Radio",
radioGroupName: "chosenAlphabet",
type: "radio",
options: ["a", "b", "c"]
}
]
};
I've changed the name attribute in your field rendering function.
<input
onChange={event => {
this.handleFormStateChange(event, index);
}}
key={option}
type={field.type}
name={field.radioGroupName}
value={option}
/>
Related
I have a React component that renders an array with an input field and a radio button. The input field is working fine but when clicking the radio button even though the state gets updated, the value is not reflected in the UI.
Following is the implementation of the React component
import React, { useState } from 'react';
import './style.css';
export default function App() {
const [subTemplate, setSubTemplate] = useState([
{ name: 'main', outputFormat: 'html' },
{ name: 'something else', outputFormat: 'text' }
]);
const handleInputChange = (value, row, key) => {
const updatedSubTemplate = subTemplate.map(item => {
if (item.name === row.name) {
return {
...item,
[key]: value
};
}
return item;
});
console.log('updatedSubTemplate', updatedSubTemplate); // updatedSubTemplate consists of the modified value
setSubTemplate(updatedSubTemplate);
};
const renderSubTemplates = () => {
return subTemplate.map((item, index) => {
return (
<div key={index}>
<input
type="text"
value={item.name}
onChange={e => {
handleInputChange(e.target.value, item, 'name');
}}
/>
<div>
<input
type="radio"
name="html"
checked={item.outputFormat === 'html'}
onChange={e => {
handleInputChange(e.target.name, item, 'outputFormat');
}}
/>
HTML
</div>
<div>
<input
type="radio"
name="text"
checked={item.outputFormat === 'text'}
onChange={e => {
handleInputChange(e.target.name, item, 'outputFormat');
}}
/>
TEXT
</div>
</div>
);
});
};
return <div>{renderSubTemplates()}</div>;
}
Following is a stackblitz link for the above implementation : https://stackblitz.com/edit/react-ugat24
Note: The radio button works as expected if there's only 1 element in the array.
It is because you have different value in name attribute.
Radio buttons are used to select one option from a list of options. so they need to have the same name to group them .
<input
type="radio"
name={`group-${index}`}
checked={item.outputFormat === 'html'}
onChange={e => {
handleInputChange('html', item, 'outputFormat');
}}
/>
Working Sample
https://stackblitz.com/edit/react-4xxpev
There are 2 issues in your code.
You are passing the same name attribute for all the radio inputs.
You are passing the wrong value for the radio elements.
Use template literals to append the index value to each set of radio group elements in the array.
Here's working code
References
Radio Input
Template literals
I'm using react-select library to add dropdown menu for my app, but "required" doesn't work (I read a discussion on GitHub for this library, creators haven't added that function yet), so for now how do I make select required? (I read here another post with the same problem, solution presented there didn't work for me). My code is:
import React, { useState } from "react";
import Select from "react-select";
export default function App() {
const [data, setData] = useState();
const options = [
{ value: "1", label: "1" },
{ value: "2", label: "2" },
{ value: "3", label: "3" },
{ value: "4", label: "4" }
];
const handleSubmit = (e) => {
console.log(data);
};
return (
<div className="App">
<form onSubmit={handleSubmit}>
<input required placeholder="name" />
<Select
options={options}
onChange={(e) => setData(e.value)}
value={options.filter(function (option) {
return option.value === data;
})}
label="Single select"
placeholder={"Select "}
menuPlacement="top"
required
/>
<button> Submit</button>
</form>
</div>
);
}
code sandbox
In my code I have a couple of regular inputs (where required works fine) but I want the Select also be required. Any suggestions are greatly appreciated.
I would think outside the box — in this case the Select component. What can you add that gets the same result? I took a quick stab at this for you:
const [isValid, setIsValid] = useState(false);
// This effect runs when 'data' changes
useEffect(() => {
// If there is data, the form is valid
setIsValid(data ? true : false);
}, [data]);
...
return (
<div>
<Select ... />
{!isValid && <p>You must choose a value</p>}
<button disabled={!isValid}>Submit</button>
</div>
)
https://codesandbox.io/s/vigilant-wiles-6lx5f
In this example, we check the state of the Select component — if it's empty (the user hasn't chosen anything) the form is invalid and the submit button is disabled.
I am making a react application where checkboxes for jobtitles are populated from the dynamic data from api and it will be exactly like given snippet.
const departments =
[
{
"sectorId":29,
"sectorName":"Building Materials Mfg. & Distribution",
"departments":[
{
"deptName":"Manufacturing",
"jobTitles":[
{
"453":false,
"JobTitleID":453,
"DepartmentID":101,
"JobName":"Consultant",
"Deleted":false,
"SortOrder":5
},
{
"323":true,
"JobTitleID":323,
"DepartmentID":101,
"JobName":"Quality Control",
"Deleted":false,
"SortOrder":1
}
]
},
{
"deptName":"Warehouse",
"jobTitles":[
{
"326":false,
"JobTitleID":326,
"DepartmentID":98,
"JobName":"Warehouse Supervisor",
"Deleted":false,
"SortOrder":1
}
]
},
{
"deptName":"Administration",
"jobTitles":[
{
"384":true,
"JobTitleID":384,
"DepartmentID":115,
"JobName":"Controller",
"Deleted":false,
"SortOrder":1
}
]
}
]
}
]
const handleJobTitle = (event, job) => {
const { checked } = event.target;
if (checked) {
document.getElementById(job.JobTitleID).checked = true;
} else {
document.getElementById(job.JobTitleID).checked = false;
}
console.log(document.getElementById(job.JobTitleID));
};
const App = () => (
<div> {departments && departments.map((levelOne, i) => (
<div
key={i}
>
<p> {levelOne.sectorName} </p>
{levelOne.departments.map((levelTwo, j) => (
<div key={j}>
<p >
{" "}
{levelTwo.deptName}{" "}
</p>
{levelTwo.jobTitles.map((job, l) => (
<div
key={l}
>
<input type="checkbox" id={job.JobTitleID} onChange={(e) => {handleJobTitle(e, job)}} name={job.JobName} checked={job[job.JobTitleID]}/>
<span>{job.JobName}</span>
</div>
))}
</div>
))}
</div>
))} </div>
)
// Render it
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>
Here the jobTitles data will be like,
"jobTitles":[
{
"453":false,
"JobTitleID":453,
"DepartmentID":101,
"JobName":"Consultant",
"Deleted":false,
"SortOrder":5
},
{
"323":true,
"JobTitleID":323,
"DepartmentID":101,
"JobName":"Quality Control",
"Deleted":false,
"SortOrder":1
}
]
And I make the checkbox checked based on the value of "453":false like,
<input type="checkbox" ...... checked={job[job.JobTitleID]}/>
And the checkboxes are checked here but when I try to uncheck the checkbox in onChange handler like,
const handleJobTitle = (event, job) => {
document.getElementById(job.JobTitleID).checked = false;
}
The checkboxes are not unchecked.. If I console/inspect the input element then the checked atribute is not removed.
Additional Info: It is also not possible to check the checkbox which is in unchecked state.. So in common I couldn't make any change to checkbox..
Could anybody please help me to achieve the solution and I am got stuck for too long with this as I am new to react..
Big thanks in advance.
Issue(s)
Direct DOM mutation is an anti-pattern in react (it is outside react so react can't know to rerender).
The handler also doesn't access the checked property of the onChange event.
const { checked } = event; // <-- should be event.target
Solution
Use react state to maintain the checked status in component state.
Move the departments data into component state.
const [departments, setDepartments] = useState(departmentsData);
Move the handleJobTitle handler into the component. I suggest using a curried function that accepts the sector id, department name, and the job id, you'll need these to "trace" the path to the correct nested object. You don't need to worry about getting the checked property from event.target as you can simply invert a checked value in state via the JobTitleID key value.
The idea here is to shallow copy any nested state, into new object references for react, that is being updated.
const handleJobTitle = (sectorId, deptName, JobTitleID) => () => {
setDepartments((data) =>
data.map((sector) =>
sector.sectorId === sectorId
? {
...sector,
departments: sector.departments.map((dept) =>
dept.deptName === deptName
? {
...dept,
jobTitles: dept.jobTitles.map((job) =>
job.JobTitleID === JobTitleID
? {
...job,
[JobTitleID]: !job[JobTitleID]
}
: job
)
}
: dept
)
}
: sector
)
);
};
Now simply attach the handleJobTitle to the input's onChange handler.
<input
type="checkbox"
id={job.JobTitleID}
onChange={handleJobTitle( // <-- pass the required values
levelOne.sectorId,
levelTwo.deptName,
job.JobTitleID
)}
name={job.JobName}
checked={job[job.JobTitleID]}
/>
You need to define an Id parameter in the input tag.
<input type="checkbox" id="JobTitleCheckBox" ...... checked={job[job.JobTitleID]}/>
Then use that id to fetch the node and change its status
const handleJobTitle = (event, job) => {
document.getElementById(job.JobTitleID).checked = false;
}
I am using react-select for autocomplete and option related field. When i select the option it passes whole that option object as {value: 'abc', label: 'ABC'} but i wanted only to pass the value as a string not the object. Thus, i used getOptionValue but it is not working as expected.
This is what I have done
<Field
name='status'
component={SearchableText}
placeholder="Search..."
options={status}
styles={styles}
getOptionLabel={option => option.label}
getOptionValue={option => option.value}
/>
I have used both getOptionLabel and getOptionValue but is still passing the selected option in object form instead of just the value as string.
Expected one
status: 'active'
Current behavior
status: { value: 'active', label: 'Active'}
I couldn't find getOptionValue in the docs for react-select, but you could always create an adapter around react-select. i.e. create your own Select component that uses react-select's Select component internally. After doing this it becomes possible to create your own getOptionValue. You can use this to make sure the value is a string.
import React from "react";
import Select from "react-select";
class MySelect extends React.Component {
getSelectValue() {
return this.props.options.find(
option => this.props.getOptionValue(option) === this.props.input.value
);
}
render() {
console.log("value", this.props.input.value);
return (
<Select
value={this.getSelectValue()}
onChange={option => {
this.props.input.onChange(this.props.getOptionValue(option));
}}
options={this.props.options}
/>
);
}
}
MySelect.defaultProps = {
getOptionValue: v => v
};
const MyForm = reduxForm({ form: "MyForm" })(
class extends React.PureComponent {
render() {
return (
<Field
name="myCoolSelect"
component={MySelect}
options={[
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
]}
getOptionValue={option => option.value}
/>
);
}
}
);
The above is a basic example of how to get this working. You may want to pass other input or meta props to take advantage of other redux-form features. e.g. onBlur, onFocus, etc. You can see it in action here: https://codesandbox.io/s/6wykjnv32n
Given the following data, how can I get the birds name and push it (Using the add button) to a new array to be displayed in another div (Using react es6)? So basically I want a user to click a bird from the semantic dropdown and display it in a different div for example shown below. This is probably simple but I can't seem to find a way to it when I'm using Semantic elements. Do I need to use onChange?
I need to to do this in a class I am exporting (react) (just havent shown the class/constructor/state definitions)
<div>
<p>How can i display 'Bird_Name' here?<p>
</div>
addClick = () => {
}
const {
Button,
Container,
Divider,
Dropdown,
Header,
Message,
Segment,
} = semanticUIReact
const birds = [
{
"value": "001",
"Bird_Name": "Eurasian Collared-Dove"
},
{
"value": "002",
"Bird_Name": "Bald Eagle"
},
{
"value": "003",
"Bird_Name": "Cooper's Hawk"
},
];
const options = birds.map(({ ID, Bird_Name }) => ({ value: ID, text: Bird_Name }))
const App = () => (
<Container>
<Divider hidden />
<Header as='h1'>Semantic-UI-React</Header>
<Dropdown
placeholder='Select...'
selection
search
options={options}
renderLabel={({ Bird_Name }) => 1}
/>
<button className="ui primary button add" onClick={this.addClick}>Add</button>
</Container>
)
// ----------------------------------------
// Render to DOM
// ----------------------------------------
const mountNode = document.createElement('div')
document.body.appendChild(mountNode)
ReactDOM.render(<App />, mountNode)
So, what you basically want is the onChange function which will display.
<Dropdown
placeholder='Select...'
selection
search
options={options}
renderLabel={({ Bird_Name }) => 1}
onChange={this.getBird}
/>
and make a getBird function
getBird = (event, {value}) => {
console.log(value);
let bird_name = event.target.textContent;
console.log(bird_name);
}
The value and text variable in the getBird function are basically the value and bird_name of the selected bird from the dropdown.