react-table conditionally select row - javascript

We're using react-table's useRowSelect for row selection. We want to extend it such that we can define rows that are non-selectable based on a certain condition, in which case they will not be selectable - neither when clicking on the non-selectable row nor when clicking Select All.
Any ideas would be greatly appreciated.

Dunno if you found your answer since but i ran into kinda a similar issue.
You can use conditionnal statement to disable a row check box quite simply.
in your useTable() you pass your props like this to your checkbox :
DataTable.js
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
selectedFlatRows,
state: { selectedRowIds },
} = useTable(
{
columns,
data,
initialState: {
selectedRowIds: INITIAL_SELECTED_ROW_IDS
}
},
useRowSelect,
hooks => {
hooks.visibleColumns.push(columns => [
{
id: 'selection',
Header: ({ getToggleAllRowsSelectedProps }) => (
<Checkbox {...getToggleAllRowsSelectedProps()} />
),
Cell: ({ row }) => <><Checkbox {...row.getToggleRowSelectedProps()} {...row.values} /> </>
//HERE : You pass your rows' values to your component
},
...columns
])
},
)
then in your checkbox component you can tell if you want the checkbox to be disabled or not based on one of your props (in this case i use one of the rows value called "valide")
CheckBox.js
import React, {useState} from 'react'
export const Checkbox = React.forwardRef(({ indeterminate, ...rest }, props, ref) => {
const defaultRef = React.useRef()
const resolvedRef = ref || defaultRef
React.useEffect(() => {
resolvedRef.current.indeterminate = indeterminate
}, [resolvedRef, indeterminate])
return (
<div className="d-flex justify-content-around">
{
rest.Valide
//i check if res.Valide is true or false (res.Valide is one of my row column's value which takes a boolean)
? <><input type='checkbox' ref={resolvedRef} {...rest} className="mx-auto" /></>
: <><input type='checkbox' ref={resolvedRef} {...rest} className="mx-auto" disabled/></>
}
</div>
)
})
if res.Valide is true then the checkbox is rendered normally, else it's disabled so you won't be able to check it.

I managed to come up with a full solution to this problem, by adapting an answer recently posted in this GitHub issue
In my case, I needed this to work with pagination and with Select Page / Select All (as opposed to only Select All).
I managed to create a solution - based on the solution from the GitHub issue thread - which supports these requirements as well. I then extracted it to an easily reusable helper function. Below is the helper function followed by an example of usage.
The helper function
import { HeaderProps, Row } from 'react-table'
interface GetConditionalSelectHeaderCheckboxProps {
/** react-table's header props */
headerProps: React.PropsWithChildren<HeaderProps<any>>
/** A predicate - based on your business logic - to determine whether a given row should be selectable */
checkIfRowIsSelectable: (row: Row<any>) => boolean
/** Whether to allow page selection. Default: true */
shouldSelectPage?: boolean
}
/**
* A convenience method for react-table headers for allowing conditional select
* #param headerProps react-table's header props
* #param checkIfRowIsSelectable A predicate - based on your business logic - to determine whether a given row should be selectable
* #param shouldSelectPage Whether to allow page selection. Default: true
* #returns Modified `checkboxProps` to enforce the conditional select
*/
export const getConditionalSelectHeaderCheckboxProps = ({
headerProps,
checkIfRowIsSelectable,
shouldSelectPage = true,
}: GetConditionalSelectHeaderCheckboxProps) => {
// Note that in my comments I differentiate between the standard logic and the logic for the conditional select
const checkIfAllSelectableRowsSelected = (rows: Row<any>[]) =>
rows.filter(checkIfRowIsSelectable).every(row => row.isSelected)
// Standard: Here we define the selection type for the next click: Select Page / Select All
const isSelectPage =
shouldSelectPage &&
headerProps.page
// For conditional select: Filter the rows based on your business logic
.filter(checkIfRowIsSelectable)
// Standard: `isSelectPage === true` if some of the rows are not yet selected
// This (standard) logic might be confusing to understand at first, but - as a side note - the idea is as follows:
// This is the variable that defines whether the header props that will be received FOR THE NEXT CLICK will be for Select Page or for Select All
// Try to play this out in your head:
// - Initially, none of the rows are selected, so when we clicking the button initially, we will select only the (selectable) rows on the page (i.e. Select Page), hence the next click will be for Select All, hence `isSelectPage` will be `false`
// - When clicking again, we will select the rest of the (selectable) rows (i.e. Select All). The next click will again be Select All (for de-selecting all), hence `isSelectPage` will be `false`
// - Finally, when clicking again, we will de-select all rows. The next click will be for Select Page, hence `isSelectPage` will `true`
.some(row => !row.isSelected)
// Standard: Get the props based on Select Page / Select All
const checkboxProps = isSelectPage
? headerProps.getToggleAllPageRowsSelectedProps()
: headerProps.getToggleAllRowsSelectedProps()
// For conditional select: The header checkbox should be:
// - checked if all selectable rows are selected
// - indeterminate if only some selectable rows are selected (but not all)
const disabled = headerProps.rows.filter(checkIfRowIsSelectable).length === 0
const checked =
!disabled && checkIfAllSelectableRowsSelected(headerProps.rows)
const indeterminate = !checked && headerProps.rows.some(row => row.isSelected)
// For conditional select: This is where the magic happens
const onChange = () => {
// If we're in Select All and all selectable rows are already selected: deselect all rows
if (!isSelectPage && checkIfAllSelectableRowsSelected(headerProps.rows)) {
headerProps.rows.forEach(row => {
headerProps.toggleRowSelected(row.id, false)
})
} else {
// Otherwise:
// First, define the rows to work with: if we're in Select Page, use `headerProps.page`, otherwise (Select All) use headerProps.rows
const rows = isSelectPage ? headerProps.page : headerProps.rows
// Then select every selectable row
rows.forEach(row => {
const checked = checkIfRowIsSelectable(row)
headerProps.toggleRowSelected(row.id, checked)
})
}
}
// For conditional select: override checked, indeterminate and onChange - to enforce conditional select based on our business logic
return {
...checkboxProps,
checked,
indeterminate,
onChange,
disabled,
}
}
Example usage:
const columns = [
{
accessor: 'foo',
Header(props) {
const checkboxProps = getConditionalSelectHeaderCheckboxProps({
headerProps: props,
// Your business logic, e.g.
checkIfRowIsSelectable: row => row.original.someData !== 'some value'
})
return <Checkbox {...checkboxProps} />
},
// ...
]

Related

how to stop rerendering in react on checkbox check?

I have simple list which is dynamically added on add button click. in my list there is a checkbox is also present .so I have an issue when I toggle the checkbox my whole list is re render why ?
let take example I added A,B,C,D in my list when I toggle D checkbox it should only render D item currently it render whole list why ?
here is my code
https://codesandbox.io/s/stupefied-wildflower-gv9be
const Item = ({ text, checked, onCheckedHandler }) => {
console.log(checked, "ssss");
return (
<div className={checked ? "bg" : ""}>
<span>{text}</span>
<input type="checkbox" onChange={e => onCheckedHandler(e, text)} />
</div>
);
};
Every time items changes (whether by adding a new item or checking a value), you are creating a new onCheckedHandler in your App. This propagates down to your Item component. Since the previous onCheckedHandler property is not referentially equivalent to the previous one, it renders (and you see that console log for each item). Memoizing the component alone won't help because a property being passed to it is changing every time.
To get around that, you need to memoize the onCheckedHandler, try this:
const onCheckedHandler = useCallback((e, selectedText) => {
const target = e.target
setItems(items => {
const i = items.findIndex(i => i.text === selectedText);
let obj = items[i];
obj.checked = target.checked;
return [...items.slice(0, i), obj, ...items.slice(i + 1)];
})
}, [setItems])
The you can wrap your Item compoennt with React.memo, and it should work as expected. You'll also need to import the useCallback the same way you import useState

React select default value on render doesn't change the selected input value + title on change?

After a couple of trials i have managed to create a React select dropdown with dynamic data for each sector.
Now, i have a problem that i am not able to create a default value on render.
I get all the dynamic values, but i would like to have one default value for instance: Select... i have managed to add one static, but for some reason when i switch from Select Categories the title stays at: Select...
WerkgeverList = () => {
const werkgeversArray = [];
const AlleWerkgevers = [
...new Set(this.state.opleidingen.map(q => q.opleiding_werkgever)),
];
AlleWerkgevers.map(werkgever => {
return werkgeversArray.push({ title: werkgever, value: werkgever });
});
console.log(werkgeversArray);
return werkgeversArray;
};
Can someone explain what I am doing wrong?
Keep your default value in the state, then add an event listener to track the changes and update the state
<select value={current_value} onChange={() => onChangedValueHandler()} >

Setting Default values for MUI toggle button group

I am using the Material UI library, and used the button toggle widget as a picker.
Here is my codesandbox - https://codesandbox.io/s/50pl0jy3xk
There are a few interactions going on, but basically a user can click a membership type for an adult, child or infant. There are 3 options of yes, no and maybe. Once a user selects one or multiple options this gets submitted to api.
I have added a default values from my receiving prop, so if a user has already got a membership it has them highlighted.
const selected = [
{
personId: "0001657",
fullName: "Joe Bloggs",
seniorStatus: "Yes",
defaultStatus: [
{ membership: "Seniors", defaultvalue: "Yes" },
{ membership: "Juniors", defaultvalue: "No" }
]
}
];
As you can see the defaultStatus field(above) has been added for the users current memberships. All this needs to do is highlight the relevant toggle, the data if not changed does not need submission to api.(below) - I would guess that the value needs to be interrogated but the value is already being used in my case.
<ToggleButtonGroup
className={classes.toggleButtonGroup}
value={value[rowIdx][cellIdx.value]}
exclusive
onChange={(event, val) =>
this.handleValue(event, val, rowIdx, cellIdx.value)
}
>
<ToggleButton
className={`w-100 ${classes.toggleButton}`}
value="yes"
>
Yes
</ToggleButton>
<ToggleButton
className={`w-100 ${classes.toggleButton}`}
value="no"
>
No
</ToggleButton>
<ToggleButton
className={`w-100 ${classes.toggleButton}`}
value="maybe"
>
Maybe
</ToggleButton>
</ToggleButtonGroup>
Any help or assistance would be fantastic!
You are using "yes", "no" and "maybe" in ToggleButton group, but using "Yes", "No" etc in default value. I guess that is a typo, both need to match.
Now you can set the value like this:
value={
value[rowIdx]["defaultStatus"] && // check if defaultStatus exists
value[rowIdx]["defaultStatus"].filter( // filter the defaultStatus
ds => ds.membership === cellIdx.label // and look for the specific membership type
)[0] // check if that exists
? value[rowIdx]["defaultStatus"].filter(
ds => ds.membership === cellIdx.label
)[0].defaultvalue // set the value
: null // otherwise null
}
Here is your forked example: https://codesandbox.io/s/mjk9zy5rqp?fontsize=14
PS:
I noticed the logic of handleValue will not work anymore. Looks like it was a 2D array previously and now you have defined it according to a new schema. May be you are in the middle of an edit. But so you know.
I think it would be better if you make defaultStatus like this, instead of an array:
defaultStatus: {
Seniors: "yes",
Juniors: "no"
}
This way you won't have to filter the array like I did in the example. You could simply use value[rowIdx]["defaultStatus"][cellIdx.label] as value.
Update:
Your handleValue function is not working because you are setting the state in wrong place (probably from your old state design). You need to modify it to incorporate you new state design:
handleValue = (event, val, rowIdx, cellIdx) => {
...
if (!newValue[rowIdx]["defaultStatus"]) { // check if you have defaultStatus for that row
newValue[rowIdx]["defaultStatus"] = []; // create empty array if that doesn't exist
}
const updatable = newValue[rowIdx]["defaultStatus"].filter( // find the column corresponding to the membership
ds => ds.membership === cellIdx
);
if (updatable[0]) { // if such column exists
updatable[0]["defaultvalue"] = val; // update the defaultvalue
} else {
newValue[rowIdx]["defaultStatus"].push({ // otherwise create a new defaultvalue
membership: cellIdx,
defaultvalue: val
});
}
this.setState({
value: newValue
});
};
See my updated code in the same sandbox url. Again, the logic could be much simplified with a plain object instead of an array.

Change all cell values in table column, already using 2D array for row and cell

https://codesandbox.io/s/1p770371j
The above demo shows a table where you can change data in each sell and the 2d array records which row and column cell the data change occurred. I have just added a column button to the colum header titles.
The idea is that the button will change all the column values to the same value. It opens up a dialog that has the 3 options. Once clicked all the options within that column changes accordingly.
This is the handle that returns each individual cell/row data upon change, which is fine.
handleValue = (event, val, rowIdx, cellIdx) => {
const newValue = [...this.state.value];
newValue[rowIdx][cellIdx] = val;
this.setState({
value: newValue
});
};
I have a new handle for the update all column:
handleChange = name => event => {
this.setState({ [name]: event.target.value });
};
which just changes the select value at the minute, but this value will need to update all in that column, guessing it will need to look like the above handle something like this...
handleChange = event => {
const newAllValues = [...this.state.value];
newAllValues = [event.target.value]
this.setState({
value: newAllValues
});
};
Any assistance in this would be great, will I need to introduce a 3rd array property for the column?
You can send handleChange which column you intend to update as argument and update each row against that column name.
I also think (I could be wrong) you intend to keep the state of all "column wise" select values in state too. But currently you are keeping it against state.status. This way, selecting "Yes" in one column would change other columns select menu to "Yes", right?
So I changed the status to be an object:
state = {
value: selected,
status: {}
};
Now logic of updating status should be added to your handleChange function too. The idea is similar, update whichever column's select menu has been changed.
So the handler becomes:
// take `column` as parameter
// update each row's `column` value. i.e. row[column]
handleChange = column => event => {
const newValue = this.state.value.map(row => ({
...row,
[column]: event.target.value
}));
this.setState({
// status is now object, save whichever column just been selected
status: {
...this.state.status,
[column]: event.target.value
},
// set value to newly calculated rows
value: newValue
});
};
I also made minor modifications in your render method to adapt to new shape of your state and new signature of handleChange.
For exmaple:
<Select
native
value={this.state.status[mItem.value]} // i.e. status['Seniors']
onChange={this.handleChange(mItem.value)} // sends 'Seniors' as argument
input={<Input id="status-native-simple" />}
>
//...
</Select>
Here is the forked sandbox:

How to see values in Semantic-UI-React with multiple attribute?

This is a really straight forward question. Notice I have the multiple attribute. When I select a value it just pushes a value into an array. Hence, if I have multiple values it will give me an array with multiple values but not the specific option I'm choosing.
Now my question is how do I get that specific value when I remove it from the Dropdown? I've searched google for an hour and haven't found an answer.
<Dropdown
options={options}
onChange={this.onChange.bind(this)}
search fluid multiple selection/>
onChange(e, { value } ) {
e.preventDefault();
// e.target.value DOESN'T WORK???!!!
}
So Dropdown from Semantic UI doesn't seem to provide this functionality out of the box. However, there's a decent way of achieving this. First, create a state like:
this.state = {
selected: [],
}
Then, bind a function to the Dropdown component like so:
<Dropdown
placeholder="Skills"
fluid
multiple
selection
options={options}
onChange={this.handleChange}/>
Once that's done, write the handleChange function to compare array lengths on every change. If state array is longer than whatever array you get from the dropdown, the item has been removed and you can check which one. Otherwise, dump the array into the state.
handleChange = (e, { value }) => {
if (this.state.selected.length > value.length) { // an item has been removed
const difference = this.state.selected.filter(
x => !value.includes(x),
);
console.log(difference); // this is the item
return false;
}
return this.setState({ selected: value });
};
You'll need a polyfill to run in IE8 and below though.

Categories

Resources