React (Reakit): How to ask for confirmation, when toggling a checkbox? - reactjs

I'm wondering, if there's a way to ask for confirmation with Reakit's checkbox. I'm using Reakit, since I found a quick way to get it to read database's boolean information, but I welcome other methods too!
I'm used to doing confirmations with buttons with async and window.confirm:
<button onClick={async hiStackOverflow => {
if (window.confirm("Want to do this?")) {
// saving to database here
}
}}>
But I didn't figure out how to do it with a checkbox. In short, I want for the page to confirm (and then save to database), when the user toggles on/off the checkbox.
// personData = database table, with boolean "recurring"
// row = which entity in a table we are talking about
function CheckboxThing ({ row, personData }) {
const checkbox = useCheckboxState({state: personData[row].recurring});
return (
<div className="checkbox-admin-other">
<Checkbox
{...checkbox}
// what here?? onClick or something?
/>
</div>
);
}

Reakit's checkbox can be used like this:
const toggle = () => setChecked(!checked);
return <Checkbox checked={checked} onChange={toggle} />;
This means that the checkbox will be checked if the variable 'checked', which needs to be put in the state of your React component, is true and that the method called 'toggle' will be called when the user toggles the checkbox. In that method, you can put the code which will show the Confirmation Prompt and then change checked if user clicked 'Yes' or leave it as it is if they check 'No'.

You can "observe" changes on checkbox.state using React Hooks:
function CheckboxThing({ row, personData }) {
const checkbox = useCheckboxState({ state: personData[row].recurring });
React.useEffect(() => {
// checking if state has changed
if (checkbox.state !== personData[row].recurring) {
if (window.confirm("Want to do this?")) {
// saving to database here
} else {
// revert checkbox state otherwise
checkbox.setState(!checkbox.state);
}
}
}, [checkbox.state, checkbox.setState, personData[row].recurring]);
return (
<div className="checkbox-admin-other">
<Checkbox {...checkbox} />
</div>
);
}
With React.useEffect, the user will see the checkbox checked before window.confirm opens. But you can use React.useLayoutEffect instead if you want it to open before checkbox state changes on the UI.

After coding around a little while, I found the solution! It turns out, you can put async inside Reakit Checkbox. Thanks to Tomislav and Diego, their answers helped me try different things and get it clean!
Here's the full function:
// admin can edit the right to join back to the queue after getting to the front
function RecurringBox ({ row, personData }) {
// sets the original values
const checkbox = useCheckboxState({state: personData[row - 1].recurring});
return (
<Checkbox {...checkbox} onChange={async checkboxSwitch => {
if (window.confirm("Change it?")) {
checkboxSwitch.persist();
// saving it to the database
await put(`${process.env.API_PATH}/person`,
{
"id": personData[row - 1].id,
"name": personData[row - 1].name,
"recurring": checkboxSwitch.target.checked
});
reload(`${process.env.API_PATH}/person`);
} else {
return null;
}
}}/>
);
}

Related

Checkmark group of checkboxes based on array values

Forgive me there are a lot of questions asking this same thing but from over 10+ years ago.
Is there any way to checkmark a group of checkboxes based on an array in React? I have an array saved within state (stepThree) that I need to pulldown when a user returns to this screen within a multistep form. I'm looking for a way that the values within that array become/stay checked upon return to that screen so it shows the user their previous selections.
Current set-up explained below
State is opened with empty checkedBox array and stepThree initialized to pull responses later. checkedBox is eventually cloned into stepThree.
this.state = {
checkedBox: [],
stepThree: this.props.getStore().stepThree,
};
Boxes that are checked by the user are added to checkedBox array or removed if unchecked.
handleCheckboxChange = (event) =>{
const isChecked = event.target.value; //Grab the value of the clicked checkbox
if (this.state.checkedBox.includes(isChecked)) {
// If the checked value already exists in the array remove it
} else {
// If it does not exist, add it
}
}
Validate and store the completed array on clicking next
if (Object.keys(validateNewInput).every((k) => { return validateNewInput[k] === true })) {
if (this.props.getStore().stepThreeObjects != this.state.checkedBox) { // only update store of something changed
this.props.updateStore({
// Store the values of checkedBox inside stepThree and run updateStore to save the responses
});
} else {
// Return an error
}
Sample checkbox
<label className="choice-contain">
<span>Checkbox Sample</span>
<input
value="Checkbox Sample"
name="Question 3"
type="checkbox"
onChange={this.handleCheckboxChange}
/>
</label>
I've tried to create a persistCheckmark function that pulls the values of the array from stepThree and then does a comparison returning true/false like I do in the handler but since this is not an event I can't figure out how to trigger that function on return to the step.
Currently when returning to the step nothing is checked again and I believe that has to do with checkedBox being initiated as empty.
persistCheckmark(event) {
const isChecked = event.target.value; //Grab the value of the clicked checkbox
if (this.state.stepThree.includes(isChecked)) {
return true;
} else {
return false
}
}
Figured it out thanks to an old post here: How do I set a group of checkboxes using values?
Just updated the filter for when the component mounts
componentDidMount() {
if (this.state.stepThree != undefined) {
var isChecked = this.state.stepThree
$('input[type="checkbox"]').filter(function() {
return $.inArray(this.value, isChecked) != -1;
}).prop('checked', true);
} else { return }
}
and then added a ternary in the state initiation to check the storage and copy it over so it doesn't initialize as empty every time.
checkedBox: this.props.getStore().stepThree != undefined ? this.props.getStore().stepThree : [],

React state value is not retained when value is set using useState within useEffect with dep []

I am using Fluent UI Details List and trying to make row editable on icon click.
My code is as below. On first run, it shows grid with empty data. Then it goes to useEffect (I tried both useEffect and useLayoutEffect, same behaviour) and data is fetched and stored in state. It also fires the render and the grid shows all the rows per the data. All good so far.
When row > cell 1 is double-clicked on the grid, I turned the row to editable mode. That is also working.
For each editable column, I have a different onChange event attached. So, when any input text box/dropdown cell value changes, it fires the respective onChange event callback function.
Within this cell change callback event, it gets item id and final changed value as input aurguments. And using them, data array will be updated and stored in state. This is my expectation.
But when I read the current data array from state within the callback function, it is empty.
So, basically, problem is that state value stored from useEffect is not retained.
There is no other code line where data array state is updated. So, no chance that the data array is reset by code.
If anyone has any idea or faced, solved such issue, let me know. Thanks in advance.
Adding few things which I tried,
I tried using the class component and it worked. only difference is that instead of useEffect, I used componentDidMount and instead of useState, I used this.setState. Not sure what is other difference within class and function component?
The same code works in class component but not in function component.
I tried using the same function component and instead of async call within useEffect, I made direct sync fetch call before render and loaded data in state as initial value. Then, it worked.
So, it fails only when the data is fetched async mode within useEffect and set to state from there.
My problem is resolved after converting to class component.
but want to understand what is the issue within my function component code.
/** function component */
const [dataItems, setDataItems] = useState<IScriptStep[]>([]);
const [groups, setGroups] = useState<IGroup[]>([]);
/** Component Did Mount */
useLayoutEffect(() => {
props.telemetry.log(`useEffect - []`, LogLevel.Debug);
(async () => {
let scriptSteps = await props.dataHelper.retrieveScriptSteps();
setDataItems(scriptSteps);
let groups = getGroups(scriptSteps);
setGroups(groups);
props.telemetry.log(`Data updated in state`, LogLevel.Debug);
})();
}, []);
/** Render */
return (
<div className="SubgridMain">
{props.telemetry.log(`render`, LogLevel.Debug)}
<div className="List">
<DetailsList
componentRef={_root}
items={dataItems}
groups={groups}
columns={columns}
ariaLabelForSelectAllCheckbox="Toggle selection for all items"
ariaLabelForSelectionColumn="Toggle selection"
checkButtonAriaLabel="select row"
checkButtonGroupAriaLabel="select section"
onRenderDetailsHeader={_onRenderDetailsHeader}
onRenderRow={_onRenderRow}
groupProps={{ showEmptyGroups: true }}
onRenderItemColumn={_onRenderItemColumn}
onItemInvoked={_onItemInvoked}
compact={false}
/>
</div>
</div>
);
/** on change cell value callback function */
const _onChangeCellName = (entityId : string, fieldName:string, finalValue:string) => {
let currentItems = dataItems;
// create new data array
let toUpdateState: boolean = false;
let newItems = currentItems.map((item) => {
if (item.key === entityId) {
if (item.name !== finalValue) {
toUpdateState = true;
item.name = finalValue ?? null;
}
}
return item;
});
if (toUpdateState) setDataItems(newItems);
};
/** columns configuration is set as below */
let columns : IColumn[] = [
{
key: 'name',
name: 'Name',
fieldName: 'name',
minWidth: 300,
isResizable: true,
onRender: this._onRenderCellName,
},
..
..
]
/** Render Name cell */
private _onRenderCellName(item?: IScriptStep, index?: number, column?: IColumn) {
if (item) {
let stepName = item?.name ?? '';
if (item.isEditable) {
let propsNameTextEditor: ITextEditorWrapperProps = {
entityId: item.key,
fieldName: 'name',
initialText: stepName,
multiline: true,
required: true,
setFinalValue: this._onChangeCellName,
};
return <TextEditorWrapper {...propsNameTextEditor} />;
} else {
return (
<div className="ReadOnly">
<p>{stepName}</p>
</div>
);
}
} else return <></>;
};

How to check dynamically rendered checkboxes

I'm rendering some checkboxes dynamically, but currently I'm only able to check the first box, and all other boxes operate the first one. How do I get the boxes to work independently of each other?
This is typescript in React. I've tried changing the interface I'm referencing in the function, thinking I was referencing the wrong thing, but none of those worked.
This is the function:
handleCheckboxClick = (entitlement: IApiEntitlements, checked: boolean): void => {
if (checked === true) {
this.selectedEntitlementIDs.push(entitlement.id);
} else {
const index: number = this.selectedEntitlementIDs.indexOf(entitlement.id);
this.selectedEntitlementIDs.splice(index, 1);
}
//tslint:disable-next-line:prefer-const
let entitlementChecked: IEntitlementChecked = this.state.entitlementChecked;
entitlementChecked[entitlement.id] = checked;
let selectAll: boolean = false;
if (this.selectedEntitlementIDs.length === this.state.responses.apiResponses.apiClients.length) {
selectAll = true;
}
this.setState({
entitlementChecked: entitlementChecked,
selectAll: selectAll
});
console.log(this.selectedEntitlementIDs, 'hi');
console.log(entitlementChecked, 'hello');
}
And this is where it's being called:
return (
<Checkbox
checked={this.state.entitlementChecked[entitlement.id]}
data-ci-key={entitlement.id}
id='api-checkbox'
key={entitlement.id}
labelText={entitlement.label}
onChange={this.handleCheckboxClick}>
</Checkbox>
);
I expect each checkbox to be able to be checked, but currently on the first one works, and all others check or uncheck that first one.
You shouldn't keep an array as a property on the class that keeps track of selected items, this isn't tied to the React lifecycle and could potentially not update the view when you want to. Instead you should just use your map (entitlementChecked) you already have to determine if something is checked or not.
handleCheckboxClick(id) {
this.setState(prevState => ({
entitlementChecked: {
...prevState.entitlementChecked,
[id]: !prevState.entitlementChecked[id]
}
}));
}
When calling the handler method, you can just pass the id through that you need specifically.
onChange={this.handleCheckboxClick.bind(null, item.id)}
Here's a rudimentary example for more detail :)

TypeAhead with allowNew=false leaves input text when going out of focus

My users complain that they can enter new value (one that is not included in the options) even when that is not exactly the case.
When you input text, without selecting item from options and then leave the typeahead, the text stays there, which leads users to believe that new value (one that is not included in options) can be entered.
What would be the right way to deal with this?
I am quite new to frontend development, so the answer might actually be obvious.
One way to address this is to clear the typeahead when the user blurs the input unless they've made a valid selection. Here's an example:
https://codepen.io/anon/pen/qLBaYK
class BlurryTypeahead extends React.Component {
state = {
selected: [],
};
render() {
return (
<Typeahead
onBlur={this._handleBlur}
onChange={this._handleChange}
options={['one', 'two', 'three']}
ref={typeahead => this._typeahead = typeahead}
selected={this.state.selected}
/>
);
}
_handleBlur = () => {
// Check if there are selections.
if (!this.state.selected.length) {
// Clear the component if not.
this._typeahead.getInstance().clear();
}
}
_handleChange = (selected) => {
// Track the selected state
this.setState({ selected });
}
}

react-native-multiple-select storing items selected on submit

I am using react-native-multiple-select and trying to create a dropdown menu that allows users to select multiple options and then logs the options they select into an array.
At the moment, my code is:
onSelectedItemsChange = selectedItems => {
this.setState({ selectedItems });
console.log('submit button was pressed')
};
render() {
const { selectedItems } = this.state;
return (
<View style={{ flex: 1 }}>
<MultiSelect
hideTags
items={items}
uniqueKey="id"
ref={(component) => { this.multiSelect = component }}
onSelectedItemsChange={this.onSelectedItemsChange}
selectedItems={selectedItems}
selectText="Pick Items"
searchInputPlaceholderText="Search Items..."
onChangeInput={ (text)=> console.log(text)}
altFontFamily="ProximaNova-Light"
tagRemoveIconColor="#CCC"
tagBorderColor="#CCC"
tagTextColor="#CCC"
selectedItemTextColor="#CCC"
selectedItemIconColor="#CCC"
itemTextColor="#000"
displayKey="name"
searchInputStyle={{ color: '#CCC' }}
submitButtonColor="#CCC"
submitButtonText="Submit"
/>
<View>
The problem is with the submit button. I only want to record the items selected once the user has pressed submit.
At the moment it logs that the button was pressed every time a new item is selected and that does not help with storing the items selected into another array.
Any help would be great.
You can do this to get an array with the item objects that are selected:
for(var i = 0; i < selectedItems.length; i++){
this.state.selectedItemsArray.push(this.state.gasOptions[selectedItems[i]])
}
console.log(selectedItems);
This should output the array of items that are selected with each item containing the unique key and the display name.
this.state.selectedItemsArray.push(listOfObject[0].id);
I noticed that the selectedItemsArray stores only the key so its an array of keys and not list of objects. Thus, if your key is id you want to push that to the array and not all the object.
I faced the same issue before. Now I fixed it.
Follow below steps:
Go to node_modules/react-native-multi-select/index.d.ts add the code
onSubmitclick: ((items: any[]) => void), inside export interface MultiSelectProps {}
Go to lib/react-native-multi-select.js add the code
onSubmitclick: PropTypes.func, inside the static propTypes ={}
Go to the function _submitSelection() and add the code inside it
const {selectedItems, onSubmitclick } = this.props; onSubmitclick(selectedItems);
Now you return your Multiselect tag add
onSubmitclick={(value1) => getSubmit(value1)}
capture your selected value with this function
const getSubmit = (value1) => { console.log('new submit value***', value1) }
I hope, It will helpful for someone.

Resources