Checkbox doesn't respond to onChange when checked is driven by state - javascript

I have a material-ui Table and have been implementing multi-select functionality on it.
My multi select should behave as follows :
Select checkboxes only appear at the start of a row on hover (when nothing has been selected)
Once one row has been selected, all other checkboxes become permanently visible.
The checkbox in the TableHead will select / deselect all rows in the Table when toggled.
Each row has its own individual unique id.
I maintain a Set called idsOfSelectedRows via a useState hook to keep track of all rows currently selected.
Each TableRow has a Checkbox which looks like this
<Checkbox
className={
idsOfSelectedRows.size === 0 &&
styles.rowSelectionCheckboxStyle
}
onChange={() => handleRowSelectionCheckboxClick}
checked={idsOfSelectedRows.has(row.id)}
/>
and handleRowSelectionCheckboxClick looks like this
const handleRowSelectionCheckboxClick = event => {
const idOfClickedRow = event.target.attributes.getNamedItem("id").value;
//If the checkbox has just become checked then add it to the idOfSelectedRows Set, otherwise remove it.
setIdsOfSelectedRows(
event.target.checked
? new Set(idsOfSelectedRows.add(String(idOfClickedRow)))
: () => {
idsOfSelectedRows.delete(String(idOfClickedRow));
return new Set(idsOfSelectedRows);
}
);
};
My issue is that clicking on the checkboxes on the rows is unresponsive. I can select them all by clicking on the select all Checkbox in the TableHead but clicking on checkboxes in individual rows does not change their state ?
Here's a full CodeSandbox reproducing the exact issue. What do I need to do to make the checkboxes in the rows be toggleable ?

Remove the () => from the onChange assignment for a quick fix.
Why
You're declaring an anonymous inline function, but that function does not call anything (missing () that is syntax to call a function). onChange accepts a function reference. When you give it an inline function, that's the function it will call. The other option is to only pass it the function reference of handleRowSelectionCheckboxClick, and then that function will be called on change instead of the anonymous function middleman.
So your two options are:
onChange={handleRowSelectionCheckboxClick} // Just the reference
OR
onChange={(e) => handleRowSelectionCheckboxClick(e)} // New inline function that calls your function
The second way is unnecessary in most cases. The first is preferred unless you need to pass custom parameters. Here is an example of how it might be useful:
onChange={(e) => handleRowSelectionCheckboxClick(e, id)}
In your sandbox, you map over elements and each gets a change handler. To treat them separately in the handler, you can pass an extra variable to the function like above.
For example, I got your sandbox working by changing the handler and assignment to this:
const handleRowSelectionCheckboxClick = (event, idOfClickedRow) => {
...
onChange={(e) => handleRowSelectionCheckboxClick(e, row.id)}

This line: event.target.attributes.getNamedItem("id").value;
won't give you the required id, since there isn't one passed, change the chekbox like so:
<Checkbox
id={row.id}
onChange={handleRowSelectionCheckboxClick}
checked={idsOfSelectedRows.has(row.id)}
/>
Then, retreive the ID like so:
const idOfClickedRow = event.target.attributes.id.value;
Updated codesandbox:
Like the other answers suggest, change the handleRowSelectionCheckboxClick function call like shown in the example.
If you like your solution with an anonymous function, you should add the (): onChange={() => handleRowSelectionCheckboxClick()}

Related

Pass Row ID to a custom component in MUI Datagrid Pro

I am trying to add an indeterminate state to row checkboxes based on selection status of other checkboxes inside a detail panel. To do this I am creating a custom checkbox component and doing some logic to see if indeterminate should be true, the only problem is the checkbox needs access to the row id it is associated with to do that check. Thus far I've found nothing to pass anything other than the given CheckboxProps given by MUI, which contains no row information. There is something called componentsProps where I can pass other props to a component, but I've yet to find a way to pass the particular row id to its associated checkbox. Does anyone know of a solution to this?
.
.
.
const customCheckbox = (props: CheckboxProps) => {
return <Checkbox {...props} indeterminate={someArray.includes(theRowIdThisCheckboxIsUsedIn)} />
};
<DataGridPro
{...data}
components={{
BaseCheckbox: customCheckbox,
}}
/>
hi when you use custom checkbox component you cant use onSelectionModelChange to give you rows id you must write their logic too

How to delete a specific textfield inside a table onclick of a button using reactjs & material ui

I want to delete textfield on a specific row inside a table whenever the specific row delete button is clicked, currently I am able to add text field onclick of add button and I'm able to delete the textField onclick of remove button but whenever the remove button is clicked all the textfields are getting deleted instead of the specific textfield Also the add icon needs to be next to the last text field but currently I could only achieve it having it on the 1st text field. Please someone help me out here. Image of what i have achieved so far.
As you can see I'm trying to delete textfields on 1st row ,but it is automatically deleting textfields in every row of table.
Working codesandbox: https://codesandbox.io/s/heuristic-fermat-63ixw?file=/src/App.js
Can someone please helpme out
Issue
You are adding multiple custom rows all with the same rowId, so when you filter them you are removing more than you want.
You also incorrectly compare a newRow.rowId against this.state.customRow.rowId which is of course undefined since it's an array, and update your state to nest customRow into another array.
handlingDeleteRow = (index, newRow) => {
let newdel = this.state.customRow.filter(
(newRow) => newRow.rowId !== this.state.customRow.rowId // <-- incorrect comparison
);
this.setState({
customRow: [newdel] // <-- nest array in array
});
console.log(this.state.customRow);
};
Solution
I suggest adding a guid to your added custom rows and matching on that.
import { v4 as uuidV4 } from 'uuid';
...
addCustomFile = (index) => {
this.setState({
resError: null,
customRow: [
...this.state.customRow,
{ rowId: index, fileData: false, fileName: false, guid: uuidV4() }
]
});
};
When deleting use the new guid property.
handlingDeleteRow = (index, newRow) => {
let newdel = this.state.customRow.filter(
(row) => row.guid !== newRow.guid
);
this.setState({
customRow: newdel
});
};
Demo
I read your code, and it is working fine.
One thing you would like to add is the index of text inputs in a single row.
Problem
Currently, if user presses + button three times, addCustomFile will add three elements to state.customRow. However, three of these elements are identical, and there is nothing to distinguished from each other.
That is why the filter function (which is executed when X button is pressed) will remove all the elements inside state.customRow that belongs to same row.
Solution
I suggest you to add a new attribute to elements of state.customRow to distinguish added inputs of a same row. For example, it could be insideRowIndex and each added input can have incrementing integer (0,1,2,3,4 ...).
Checking insideRowIndex inside the filter function will let you to only remove the input you intend to, not all the others that belong to same function.

ReactJS - update component (generated from a list) in the same div

It's a basic question but I searched for a guide without success...
I want to have a list of dropdowns and inputs and each dropdown will change the input next to it.
var list = [{ name: "foo1"}, {name: "foo2"}];
return (
{list.map( (name) => {
return (<div>
<dropdown data={someData}
onChange={(ev) => {
if(ev.value == 'clearIt')
<CHANGE THE NEAR INPUT VALUE>
}
}/>
<input value={name.name} />
</div>)
})});
I don't want to use DOM nor ref cause I understood that it's better to avoid it.
Any suggestions?
Or maybe the ref is the only option?
Thanks
So you can achieve this by doing the following steps:
Create a new component and move the dropdown and input to this new component.
Add state to your component by following this example:
https://reactjs.org/docs/state-and-lifecycle.html#adding-local-state-to-a-class
Add an event listener onChange to the dropdown with an event handler which can update the state you created in the first step. (Remember to bind the handler in the constructor.
Add the new component within the div element of this example you gave and pass the relevant data you need to the new component you created.
This should allow you to update only the input next to the dropdown. Also it allows you to have different data for each dropdown you created.

React onLoad callback should set state of element, but does not do so, because onLoad is not called

I have a load of custom checkboxes that look like this:
<CustomCheckbox className='' name='foo' text='Foo' defaultState={false} onChange={this.handleChange} onLoad={this.handleChange}/>
Which are components of this form:
export const CustomCheckbox = (props) => {
return (
<label htmlFor={props.name} className={`customCheckboxLabel ${props.className}`}>
<input type='checkbox' name={props.name} id={props.name} className='customCheckboxInput' onChange={props.onChange} onLoad={props.onLoad} defaultChecked={props.defaultState}/>
<span>{props.text}</span>
</label>
)
}
onChange:
handleChange = e => this.setState({ [e.target.name]: e.target.checked })
In terms of onChange, this works fine, however, onLoad is not being called. I actually want onChange and onLoad to be the same function, as that way, the checkboxes will automatically populate the state with their (initially unchecked) values, and update them to checked if the user checks them.
The reason I want to do this, is that I later need to submit the whole checkbox array (both checked and unchecked). The number and values of checkboxes are likely to be changed regularly, so I don't want to have to define foo: false for every checkbox in the constructor of the page, as this seems to violate DRY (and would be a pain, since adding a checkbox would mean adding the component, and adding it to the initial state)
Desired behaviour:
handleChange called when each checkbox initially loads, so that the state contains name: initialstate for every checkbox, handleChange called again on change, to record if checkbox state is changed from initialstate.
Current behaviour:
onLoad function is never called.

How do I programatically fill input field value with React?

I have a modal with some input fields. I can easily pass the data automatically with the user typing an input, using onChange function in the input field, as
<Input onChange={this.props.store.handleCategoryChange} type="text" />
and .. (Using a mobx store, irellevant though)
#observable categoryValue;
#action handleCategoryChange = (e, data) => {
this.categoryValue = data.value;
e.preventDefault();
};
However, I want to add a function where the user can pre-fill this with information elsewhere in the application. I have the data to pre-fill the input fields with, but I can't figure out how do actually input it programatically, without the user doing it?
I need to invoke the above handleCategoryChange function. But my variable would be equal to data.value .. Which presents a problem! Invoking this function by myself isn't possible, because I won't have the event e nor a value called data.value as I will "just" pass in a variable by itself.
What's the right way to programatically fill an input field automatically in React, using variables elsewhere? I need to invoke the onChange function somehow, but the input values will be different..
Use controlled component for this situation, define a value property of input element like this:
<Input value={variable_name} ....
Whenever you will update that variable, automatically that value will get populated in input element.
Now you can populate some default value by assigning a value to variable_name and user can update that value by onChange function.
As per DOC:
An input form element whose value is controlled by React in this way
is called a "controlled component".
Pass in the value property for input:
<input type="text" value={this.state.value} onChange={(e) => {this.setState({value: e.target.value })}/>
you can use the controlled component and pass the value to it.
<input type="text" value{this.state.value}
onChange={()=> {this.setState({value:e.target.value })}}
Good question. I'm having the same issue, and just found a solution.
The problem is that:
You can't just use the default state of the modal component to set the initial input value, because the modal renders one time within the parent component (starting off invisible), so the default state of the Modal wont keep up with any changes to the 'pre-filled' info in the store that the inputs within the modal require access to.
You can't use the value attribute of the input to reference some redux store prop, since this is needed to reference the onChange function so the user can make changes to that pre-filled info.
And, you can't use the onChange function to set the initial value, because it is required to update the local state with the users changes - not set an initial value. Anyway, this requires the user to click on something, and you want the modal inputs to be pre-populated before the user does anything as soon as the modal opens...
So. What we need is to update these input fields every time the Modal attribute isModalOpen (or whatever you are using) changes.
(ie, pre-populate the fields when the Modal is opened).
Again, note that opening the Modal is not RENDERING the modal, it was already rendered, once, and has sat there being invisible until that isModalOpen attribute changed to true.
The Solution:
Step 1: make a handler function in the Modal component that prefills the inputdata by updating the local state of the Modal component. Mine looks like this :
handlePreFillModalInputsOnOpen = () => {
this.setState(() => ({
orthForm: this.props.matchLexUnit['orthForm'],
orthVar: this.props.matchLexUnit['orthVar'],
oldOrthForm: this.props.matchLexUnit['oldOrthForm'],
oldOrthVar: this.props.matchLexUnit['oldOrthVar'],
source: this.props.matchLexUnit['source'],
namedEntityCheck: this.props.matchLexUnit['namedEntityCheck'],
styleMarkingCheck: this.props.matchLexUnit['styleMarkingCheck'],
artificialCheck: this.props.matchLexUnit['artificialCheck'],
error: ''
}));
};
Step 2: Make that function fire ONLY when the isOpen attribute of the modal changes from false to true.
(This is the meat of your problem I think).
Use the lifecycle method componentDidUpdate. This will execute every time the props of the modal change.
componentDidUpdate(prevProps) {
if (this.props.isModalOpen === true && prevProps.isModalOpen === false) {
this.handlePreFillModalInputsOnOpen();
}
}
Watch out
make sure that you have a conditional in componentDidUpdate, otherwise you can run into infinite-loop/performance issues
if you have the possibility of null values icoming in as the prefilled input info, make a null-check so that they will not be passed into the local state.
Hope this helps :)
How to Programmatically Prefill, Fill or Update input fields value in React or useState Hooks?
Firstly - Get and define the data from database
const **yourData** = isDataFromDatabase;
or if the data is stored in Redux, then...
const **yourData** = useSelector(isDataFromDatabase);
Secondly - append it as the default value of a useState hook
const [isDataValue, setIsDataValue] = useState(**yourData**);
Thirdly - create a function to watch and Update the changes made by the user to your data value and set it to the useState hook created above
/** handles Your Data Value */
const handleDataValue = (text) => {
setIsDataValue(text);
};
Lastly - in your input tag, use the useState State as the "Value" parameter so it can be updated with the onChange function
<input
className="css"
id="myDataInput"
type="text"
value={isDataValue}
placeholder={isDataValue}
onChange={(e) => handleDataValue(e.target.value)}
/>
Now when you load the component, the prefilled value will be shown and can be updated in the HTML Input field.
That's All.

Categories

Resources