Unable to check/uncheck the checkbox - javascript

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;
}

Related

React : trying to pass state to reset custom input child, why is it not updating?

I made a custom file input in my app. It's working like a charm but when the file is uploaded, the custom file input is still showing the name of the file, which is a problem.
I tried to pass a state from parent component in order to reset the name displayed but, for some reason, the child prop does not update with the parent state and I don't know why.
Here's the custom file input :
export default function CustomInput({
disabler,
setUpperLevelFile,
previousName,
typeOfFiles,
lastInput,
reset,
}) {
const [fileUpload, setFileUpload] = useState(null);
useEffect(() => {
if (lastInput) {
setUpperLevelFile && setUpperLevelFile(fileUpload, lastInput);
} else {
setUpperLevelFile && setUpperLevelFile(fileUpload);
}
}, [fileUpload]);
useEffect(() => {
reset && setFileUpload(null);
console.log("custom input use effect : ", reset);
}, [reset]);
return (
<label className="customInputLabel">
<CustomButton
buttonInnerText="browse"
/>
<p>
{(fileUpload &&
`${fileUpload.name}, (${sumParser(fileUpload.size)})`) ||
(previousName && previousName) ||
"chose a file"}
</p>
<input
type="file"
name="realInput"
className="innerFileInput"
accept={typeOfFiles && typeOfFiles}
disabled={disabler && !disabler}
style={{ display: "none" }}
onChange={(e) => {
setFileUpload(e.target.files[0]);
}}
/>
</label>
);
}
And here is some of the parent code :
export default function ImportFiles(props){
...
const [resetInputs, setResetInputs] = useState(false);
const returningInputs = () => {
let stockInputs = [];
for (let i = 0; i < filesCounter; i++) {
stockInputs.push(
<CustomInput
key={`custom input ${i}`}
setUpperLevelFile={handlingInputChange}
lastInput={i === filesCounter - 1}
reset={resetInputs}
/>
);
}
setFilesInputs(stockInputs);
};
const handlingPostingFiles = () => {
postingFiles(uploadFiles, setUploadStatus);
setResetInputs(true);
};
useEffect(() => {
console.log("edit packages use effect : ", resetInputs);
}, [resetInputs]);
...
return(
...
{filesInputs}
...
)
The console.log in parent component shows that the state is updated but the one in CustomInput doesn't trigger after first render. So it's not updated.
After realizing the process - there should be a clean 🧼 🧽 phase:
so add this line after submitting form:
setFileUpload(null)
in the function handlingPostingFiles or postingFiles.
There is also option to hide this section with that condition: !resetInputs:
<p>
{((fileUpload && !resetInputs) &&
`${fileUpload.name}, (${sumParser(fileUpload.size)})`) ||
(previousName && previousName) ||
"chose a file"}
</p>

Handle Radio on Change in React JS?

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}
/>

Handle populating state from multiple checkboxes

I've got a form which builds a list of checkboxes from some data:
<fieldset className="visibility">
<div className="input-container checkbox">
<span className="label">Visible to</span>
<ul>
{
allForces.map(force => {
if (force.name !== 'White' && force.name !== currentMarkerForce) {
return (
<li key={force.uniqid}>
<label>
<input onChange={handleVisibilityChange} name={`visibility-${_.kebabCase(force.name)}`} type="checkbox" value={force.name} checked={markerVisibleTo.includes(force.name) }/>
{force.name} cell
</label>
</li>
)
}
})
}
</ul>
</div>
</fieldset>
As it is usually at least 2 items that will appear and can be checked, I wrote a handler for it which populates an array before posting back to the state, the contents of this array is initially populated from the existing state:
const visibilityChecked = [...markerVisibleTo]
const handleVisibilityChange = ({ target }) => {
const { checked, value } = target
checked ? visibilityChecked.push(value) : visibilityChecked.pop(value)
setMarkerVisibleTo(visibilityChecked)
}
The last line is a call to a useState hook this, mostly works but sometimes I get an odd behaviour where the wrong checkbox is selected:
Can anyone please help shed some light on what is causing this problem?
I may guess that happens because state update is asynchronous and by the time you attempt to apply changes with setMarkerVisibleTo() your state is different from the one you assume it is, you may try to put const visibilityChecked = [...markerVisibleTo] into handleVisibilityChange() body:
const handleVisibilityChange = ({ target }) => {
const visibilityChecked = [...markerVisibleTo]
const { checked, value } = target
checked ? visibilityChecked.push(value) : visibilityChecked.pop(value)
setMarkerVisibleTo(visibilityChecked)
}
Or, as I would write that:
const handleVisibilityChange = ({target:{checked,value}}) => {
const visibilityChecked = checked ?
[...markerVisibleTo, value] :
[...markerVisibleTo].filter(val => val != value)
setMarkerVisibleTo(visibilityChecked)
}
You may find full-blown demo over here:
//dependencies
const { render } = ReactDOM,
{ useState } = React
//mocking source data
const checkItems = [...'abcd']
//check list component
const CheckList = ({items}) => {
const [visibleMarkers, setVisibleMarkers] = useState(checkItems),
onVisibilityChange = ({target:{checked,value}}) => {
const visibilityChecked = checked ?
[...visibleMarkers, value] :
[...visibleMarkers].filter(val => val != value)
setVisibleMarkers(visibilityChecked)
}
return (
<div>
<ul>
{
items.map((item,key) => (
<li {...{key}}>
<label>
Option {item}
<input
type="checkbox"
value={item}
checked={visibleMarkers.includes(item)}
onChange={onVisibilityChange}
/>
</label>
</li>
))
}
</ul>
<span>visibleMarkers: {JSON.stringify(visibleMarkers)}</span>
</div>
)
}
//render
render (
<CheckList items={checkItems} />,
document.getElementById('root')
)
<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>

How to clear react tags input field?

In react-tag-autocomplete lib, need to add manual suggestions.
So, I created new component called ReactTagsSuggestions for suggestions list.
How can I clear the input field of ReactTags
<ReactTags
id="form-share"
tags={selectedTags}
// suggestions={tagSuggestions}
handleAddition={this.props.handleAddition}
handleDelete={this.props.handleDelete}
placeholder={this.props.tags.length ? '' : tr('Share with users, groups, and channels')}
tagComponent={this.selectedTags}
handleInputChange={this.handleInputChange}
autofocus={false}
/>
<ReactTagsSuggestions
suggestionList={tagSuggestions}
showSuggestions={tagSuggestions.length > 0}
checkToShare={this.props.checkToShare}
/>
How about if you create a function like this
handleDelete = () => {
this.setState({ tags: [] })
}
and pass this to the ReactTags component
<ReactTags
.
.
handleDelete={ this.handleDelete }
/>
?
To clean the input field of ReactTags you can reset the selectedTags to an empty array:
clearTag() {
this.setState({ selectedTags: [] });
}
// Call the clearTag somwhere in your app
<button type="button" onClick={this.clearTag}>Clear tags</button>
Example: https://codesandbox.io/s/react-tags-v9mft
It is too late, but may be it will be useful to others. I think you mean clearing input value instead of tags. Here is solution with useState and inputValue prop of ReactTags
const [inputValue, setInputValue] = useState('');
<ReactTags
inputValue={inputValue}
handleAddition={(...args) => {
myHandleAddition(args);
setInputValue('');
}}
handleInputChange={(tag) => {
if (tag.slice(-1) === ' ') {
myHandleAddition([{ id: tag, text: tag }]);
setInputValue('');
} else {
setInputValue(tag);
}
}}
/>

ReactJSL How to target a single element in a map function with a button

I have a Component that renders a list of elements using the map function. Each element is rendered with a delete and edit button. I have added the delete functionality, but I'm having problem with the edit one.
The functionality that I want is: click on edit item, replace H3 element (which is the title) with an input field and let the user update the name. I've tried replacing an element with another but this only works for the first element of the list, because I get the element with 'getElementById' I have tried doing it with querySelector, but that selects only the last element of the array.
I have no idea what to do. I know the issue is selecting the particular element at the right index. I use an id as a key but I don't know how to properly replace the html element. Any help will be vastly appreciated.
Here is where the map function renders the elements:
class Donut extends Component {
render(){
const {donuts, deleteDonut, editDonut} = this.props;
const donutsList = donuts.map((donut) => {
return <div key={donut.id} className="donut">
<div className="name">
<img src={donut.image} />
<div id="donut-name">
<h3 id="donut-title">{donut.name}</h3>
<p>{donut.date}</p>
</div>
</div>
<div className="price">
<p>{donut.price}</p>
<img src="img/edit.png" id={donut.id} onClick={()=>{editDonut(donut.id)}} />
<img src="img/delete.png" id={donut.id} onClick={() => {deleteDonut(donut.id)}} />
</div>
</div>
})
return (
<div>
{donutsList}
</div>
)
}
}
export default Donut
Try to avoid as much as possible directly manipulating DOM elements when you using React. In this case, you should use another approach:
Add a field to this class's state: editingDonutId
When you click in a donut, set the editingDonutId to corresponding id and when you finished it, reset the value.
In render function, inside the map, do a condition render to check if current rendering donut has same id with editingDonutId, if true, we render an input instead.
You are using react, not jquery, so do not use getElementById, try react solution.
This is my solution:
class Donut extends Component {
state = {
donutsState: {}
}
setDonutState: (id, value) => {
this.setState((preState) => {
const predonutState = preState.donutsState[id] || {}
return {
donutsState: {
...preState.donutsState,
[id]: {
...predonutState,
...value,
}
}
}
})
}
getDonutState: (id) => this.state.donutsState[id] || {};
render(){
const {donuts, deleteDonut, editDonut} = this.props;
const donutsList = donuts.map((donut) => {
const donutState = this.getDonutState(donut.id)
// when user input the name, save it in the state.
const onChange = (e) => {
this.setDonutState(donut.id, { value: e.target.value })
}
// when click edit, replace h3 with input.
const onEdit = () => {
this.setDonutState(donut.id, { eidt: true })
}
// when enter key, replace input with h3 and submit the name value.
const onKeyDown = (e) => {
if (e.key === 'Enter') {
this.setDonutState(donut.id, { eidt: false })
editDonut(donut.id, {
name: this.getDonutState(donut.id).value || donut.name,
})
}
}
return (
<div key={donut.id} className="donut">
<div className="name">
<img src={donut.image} />
<div id="donut-name">
{
donutState.edit
? <input id="edit-donut-title" value={donutState.value || donut.name} onChange={onChange} onKeyDown={onKeyDown} />
: <h3 id="donut-title">{donut.name}</h3>
}
<h3 id="donut-title">{donut.name}</h3>
<p>{donut.date}</p>
</div>
</div>
<div className="price">
<p>{donut.price}</p>
<img src="img/edit.png" id={donut.id} onClick={()=>{editDonut(donut.id)}} />
<img src="img/delete.png" id={donut.id} onClick={() => {deleteDonut(donut.id)}} />
</div>
</div>
)
})
return (
<div>
{donutsList}
</div>
)
}
}
export default Donut

Categories

Resources