I am trying to pass selected row id to handleDelete() when Button is pressed. For some reason its sending id's of all objects inside array. But I only want to capture selected row id and send it to handleDelete function.
const columns = [
{
title: 'Action',
key: 'action',
sorter: true,
render: (text, row) => (
<Space size="middle">
<Button onClick = {handleDelete(row.id)} type="text"> Delete </Button>
</Space>
),
},
]
My table component
<Table
{...tableProps}
pagination={{
position: [top, bottom],
}}
columns={tableColumns}
dataSource={hasData ? data : []}
scroll={scroll}
/>
Function to recieve id : ( I am sending the ID's but its logging all the ID's )
const handleDelete = (id) => {
console.log(id)
}
Passing handleDelete(row.id) to onClick causes handleDelete to be called for each item during render time of the component.
Change the callback of onClick event handler for delete button, in a way that an arrow function returns handleDelete:
<Button onClick = {() => handleDelete(row.id)} type="text">
Delete
</Button>
You should write your delete function like this
const handleDelete = (id) => () => {
console.log(id)
}
// Usage
const columns = [
{
title: 'Action',
key: 'action',
sorter: true,
render: (text, row) => (
<Space size="middle">
<Button onClick={handleDelete(row.id)} type="text"> Delete </Button>
</Space>
),
},
]
and another way, use bind like this:
const handleDelete = (id) => {
console.log(id)
}
// usage
const columns = [
{
title: 'Action',
key: 'action',
sorter: true,
render: (text, row) => (
<Space size="middle">
<Button onClick={handleDelete.bind(this,row.id)} type="text"> Delete </Button>
</Space>
),
},
]
One option remaining, use dataset attribiutes:
const handleDelete = (e) => {
console.log(e.target.dataset.id)
}
// usage
const columns = [
{
title: 'Action',
key: 'action',
sorter: true,
render: (text, row) => (
<Space size="middle">
<Button data-id={row.id} onClick={handleDelete} type="text"> Delete </Button>
</Space>
),
},
]
Related
Let's say I have a datagrid table like so (from the official MUI documentation):
import * as React from 'react';
import { DataGrid, GridToolbar } from '#mui/x-data-grid';
import { useDemoData } from '#mui/x-data-grid-generator';
const VISIBLE_FIELDS = ['name', 'rating', 'country', 'dateCreated', 'isAdmin'];
export default function ControlledFilters() {
const { data } = useDemoData({
dataSet: 'Employee',
visibleFields: VISIBLE_FIELDS,
rowLength: 100,
});
return (
<div style={{ height: 400, width: '100%' }}>
<DataGrid
{...data}
components={{
Toolbar: GridToolbar,
}}
/>
</div>
);
}
Now let's say I wanted to filter the column 'name' in this table by onClick of a button. That button, when clicked, should give a string, let's say 'Ray'. When this button is clicked, I want to automatically filter the table so that every value in the 'name' column that contains the string 'Ray' only gets displayed.
My Approach so far
I have tried using useState from react and the filterModel prop in DataGrid so that pressing the button filters the table like so:
....
const [filt, setFilt] = useState('') // Initialize it with an empty filter
const handleClick = () => {
setFilt('Ray');
};
return (
<div style={{ height: 400, width: '100%' }}>
<DataGrid
{...data}
components={{
Toolbar: GridToolbar,
}}
filterModel={{
items: [{ columnField: 'name', operatorValue: 'contains', value: filt },
]
}}
/>
<Button onClick={handleClick}>Change Filter</Button>
</div>
);
}
This works but the problem with this approach is that it locks every other filter and the filter is basically stuck on the 'name' column and the user can now only use the button to filter the column. It doesn't even let me remove the filter.
I have also tried the onFilterModelChange prop but it didn't work; I am honestly confused on how to use it for this specific case. Any help would be appreciated.
You were close. I believe that you would need to form the entire items array instead of only a part of the filter object.
i.e. setFilt([{ columnField: 'name', operatorValue: 'contains', value: "Ray"}]) instead of setFilt("Ray") along with { columnField: 'name', operatorValue: 'contains', value: filt}
You can reset the filters by setting items: [] in the filterModel.
CodeSandbox Example with a single filter criterion.
(I had to install #mui/material v5.9.2 instead of v5.9.3 to get it to work if you have any compile issues on codesandbox)
const [filt, setFilt] = useState([])
<DataGrid
{...data}
filterModel={{
items: filt
}}
/>
<Button
onClick={() =>
setFilt([
{
columnField: "name",
operatorValue: "startsWith",
value: "A"
}
])
}
>
Name Starts With A
</Button>
<Button
onClick={() => setFilt([])}
>
Reset Filters
</Button>
Bonus: Multi-filtering with buttons
CodeSandbox Example of a multi-filter grid with buttons.
Multi-filtering is only possible with the Pro version of MUI. Without the Pro version, any additional filters added past the first one are ignored (as you discovered).
This is what I came up with using #mui/x-data-grid-pro and 3 different buttons that each apply a unique filter:
const [filt, setFilt] = useState([])
<DataGridPro
{...data}
filterModel={{
items: filt,
GridLinkOperator: GridLinkOperator.And
}}
/>
<Button
onClick={() =>
setFilt([
...filt,
{
id: 1,
columnField: "rating",
operatorValue: ">",
value: 3
}
])
}
>
Rating Greater Than 3
</Button>
<Button
onClick={() =>
setFilt([
...filt,
{
id: 2,
columnField: "name",
operatorValue: "startsWith",
value: "A"
}
])
}
>
Name Starts With A
</Button>
<Button
onClick={() => setFilt([])}
>
Reset Filters
</Button>
A caveat to be aware of --- you will need to add an id to each filter object in the items array when using multi-filtering.
e.g.
[
{ id: 1, columnField: 'rating', operatorValue: '>', value: 3 },
{ id: 2, columnField: 'name', operatorValue: 'startsWith', value: "A" }
]
remember this code not work in real time anymore
##Update: Now a day you must use onFilterModelChange ##
const [filt, setFilt] = useState({
items: [{
}]
})
<DataGrid
{...data}
filterModel={filt}
onFilterModelChange={(newFilterModel) =>
setFilt(newFilterModel)
}
/>
<Button
onClick={() =>
setFilt([
{
columnField: "name",
operatorValue: "startsWith",
value: "A"
}
])
}
>
Name Starts With A
</Button>
<Button
onClick={() => setFilt([])}
>
Reset Filters
</Button>
with onFilterModalChange props you can update your table filter
I'm mapping an array to have the Link which gonna direct me to modify one row of the table
This is my DepotList Component :
import React from 'react';
import { Link } from 'react-router-dom';
import Table from '../../../Common/Table';
import { textFilter } from 'react-bootstrap-table2-filter';
const DepotList = props => {
const { depots } = props;
const columns = [
{
dataField: 'name',
text: 'nom depot',
},
{
dataField: 'adress',
text: 'adresse',
sort: true,
},
{
dataField: 'size',
text: 'Taille depot',
},
{
dataField: 'equipements',
text: 'Equipements',
},
{
dataField: 'updated',
text: 'Mis à jour',
},
{
dataField: '',
text: 'Actions',
formatter: () => {
return (
<>
{depots.map((depot, index) => (
<div>
<Link
className=""
to={`/gestion-depots/edit/${depot._id}`}
key={index}
>
{console.log(`index ${depot._id}`)}
Modifier
</Link>
</div>
))}
</>
);
},
},
];
return (
<>
<p>{depots.length} Depots</p>
{console.log('depotlist ----', depots)}
<Table data={depots} columns={columns} csv search filter />
</>
);
};
export default DepotList;
But I'm getting n links in every action row like this :
And if i click the link it works perfectly lol : first link first row , second link second row ..etc
Can anyone help me to fix this problem !!
This is the Table Component
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory, { Type } from "react-bootstrap-table2-editor";
import filterFactory, { textFilter } from "react-bootstrap-table2-filter";
import paginationFactory from "react-bootstrap-table2-paginator";
import ToolkitProvider, {
CSVExport,
Search
} from 'react-bootstrap-table2-toolkit';
const indication = () => {
return 'Oops! No data now! Please try again!';
};
const { ExportCSVButton } = CSVExport;
const { SearchBar } = Search;
const Table = props => {
const {
data,
columns,
striped,
hover,
condensed,
csv,
search,
clickAction,
isRowEvents,
filter,
cellEdit,
pagination
} = props;
const rowEvents = {
onClick: (e, row, rowIndex) => {
clickAction(row._id, rowIndex);
}
};
const selectRow = {
mode: "checkbox",
clickToSelect: true,
clickToEdit: true,
};
return (
<ToolkitProvider
keyField='_id'
data={data}
columns={columns}
exportCSV={csv}
search={search}
filter={filter}
cellEdit={cellEdit}
pagination={pagination}
>
{props => (
<div className='table-section'>
{csv && (
<div className='csv'>
<ExportCSVButton
className='input-btn custom-btn-secondary md'
{...props.csvProps}
>
Exporter CSV
</ExportCSVButton>
</div>
)}
{search && (
<div className='search'>
<SearchBar {...props.searchProps} />
</div>
)}
<BootstrapTable
{...props.baseProps}
keyField='_id'
data={data}
columns={columns}
striped={striped}
hover={hover}
condensed={condensed}
pagination={paginationFactory()}
cellEdit={cellEditFactory({
mode: "dbclick",
blurToSave: true,
nonEditableRows: () => [1, 2, 3],
})}
noDataIndication={indication}
rowEvents={isRowEvents ? rowEvents : null}
filter={filterFactory()}
/>
</div>
)}
</ToolkitProvider>
);
};
export default Table;
depots is probably an array and in the last index of your columns you are mapping it with "Modifier" as its inner text.
formatter: () => {
return (
<>
{depots.map((depot, index) => (
<div>
<Link
className=""
to={`/gestion-depots/edit/${depot._id}`}
key={index}
>
{console.log(`index ${depot._id}`)}
Modifier
</Link>
</div>
))}
</>
);
},
Can you also share the table component, and the depots array list.
You are mapping all your depots array in your formatter so in each row you got all the links
I'm not sure of the logic you implemented in your Table component but you probably can solve the problem changing your last element of columns in this way
{
dataField: '_id',
text: 'Actions',
formatter: (_id) => {
return (
<div>
<Link
className=""
to={`/gestion-depots/edit/${_id}`}
key={index}
>
{console.log(`index ${_id}`)}
Modifier
</Link>
</div>
);
},
},
all you have to change is the parameters that you are passing to formatter in your table component based on the datafield
or you can pass the whole item to formatter and do your extraction on the single item
So I have an antd table in ReactJS that gets its data from an API call. So in my "account name" column, I have multiple values that are the same and I want to merge them into one row. I've tried looking for questions here and found this codesandbox project that does exactly what I want but when I applied it to my code, it wouldn't work. It drops the cells in my account name column and replaces it with values from the next column (Basically, moving my rows to the left and leaving the last column blank.)
UPDATE: I've added another sandbox link to recreate my problem or where I am at.
const names = new Set();
useEffect(() => {
names.clear();
});
const columns = [
{
key: "1",
title: "Account Name",
dataIndex: "account-name",
rowSpan: 1,
render: (value, row, index) => {
const obj = {
children: value,
props: {}
};
console.log(names.has(value), value);
if (names.has(value)) {
obj.props.rowSpan = 0;
} else {
const occurCount = dataSource.filter(data => data['account-name'] === value).length;
console.log(occurCount);
obj.props.rowSpan = occurCount;
names.add(value);
}
return obj;
},
sorter: (record1,record2) => record1['account-name'].localeCompare(record2['account-name']),
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm }) => {
return (
<Input
autoFocus
placeholder="Search Column..."
value={selectedKeys[0]}
onChange={(e) => {
setSelectedKeys(e.target.value ? [e.target.value] : []);
}}
onPressEnter={() => {
confirm();
}}
onBlur={() => {
confirm();
}}
></Input>
);
},
filterIcon: () => {
return <SearchOutlined />;
},
onFilter: (value, record) => {
return record['account-name'].toLowerCase().includes(value.toLowerCase());
}
},
{
key:"2",
title:'Group ID',
dataIndex:'group-id',
sorter: (record1,record2) => record1['group-id'].localeCompare(record2['group-id']),
width: '18%',
rowSpan: 1,
},
{
key:"3",
title:'Financial Account ID',
dataIndex:'financial-account-id',
sorter: (record1,record2) => record1['financial-account-id'].localeCompare(record2['financial-account-id']),
width: '15%',
rowSpan: 1,
},
{
key:"4",
title:'Industry',
dataIndex:'industry',
sorter: (record1,record2) => record1.industry.localeCompare(record2.industry),
width: '15%',
rowSpan: 1,
},
{
key:"5",
title:'Billing Address',
dataIndex:'billing-address',
rowSpan: 1,
},
];
return (
<div>
<Table
bordered={true}
loading={loading}
columns={columns}
dataSource={dataSource}
pagination={true}
/>
</div>
)
}
When I change the value of obj.props.rowSpan into 0 it drops the whole columns of values. but when I change it into 1, it looks normal but without merging the rows.
Any help would be greatly appreciated I've been trying to fix this for a week.
I have an array of objects which I'm rendering by section - see title of each object "Price", "Sectors and Charges" etc.
This populates a mini modal where users can select options to update rendered columns basically a filter.
The selection of the items are working however if I make a selection of the first item "0" all sections with the first option are selected.
How can I store the selection from each object into the selectedOptions array?
Please note I'm using react js and styled components, I've not added the styled component code.
Data:
const columnsData = [
{
title: 'Price',
options: [
{
label: 'Daily Change'
},
{
label: 'Price'
},
{
label: 'Price Date'
},
{
label: 'Volatility Rating'
}
],
},
{
title: 'Sectors and Charges',
options: [
{
label: 'Sector'
},
{
label: 'Asset Class'
},
{
label: 'AMC'
},
],
},
{
title: 'Cumulative Performance',
options: [
{
label: '1 month'
},
{
label: '6 months'
},
{
label: '1 year'
},
],
},
]
Code:
const EditColumns = ({active, onClick}) => {
const [selectedOptions, setSelectedOptions] = useState([0, 1, 2]);
const update = () => {
onClick();
}
const updateSelection = (z) => {
setSelectedOptions(selectedOptions.includes(z) ? selectedOptions.filter(j => j !== z) : [...selectedOptions, z]);
}
return (
<Wrap onClick={() => update()}>
<CTA>
<SVG src="/assets/svgs/btns/edit.svg" />
<span>Columns</span>
</CTA>
{active &&
<Dropdown>
<Head>
<span className="title">Edit Columns</span>
<span>Select the columns you would like to see</span>
</Head>
<Body>
{columnsData.map((item, i) => {
return (
<Section key={i}>
<SectionHead>
<span className="title">{item.title}</span>
<span>Select all</span>
</SectionHead>
<SectionList>
{item.options.map((child, z) => {
const selected = selectedOptions.includes(z);
return (
<li key={z} className={classNames({selected})} onClick={() => updateSelection(z)}>
<span>{child.label}</span>
</li>
)
})}
</SectionList>
</Section>
)
})}
</Body>
</Dropdown>
}
</Wrap>
)
}
export default EditColumns;
Your section lists are all sharing the same state variable, so any changes will be applied to all of them. You could fix this either by constructing a more complex state object which more closely resembles the structure of columnsData, or making each SectionList its own component with its own state. What you decide to do will depend on the degree to which the EditButtons component actually needs access to the whole state.
The second approach might look something like this:
const EditColumns = ({active, onClick}) => {
const update = () => {
onClick();
}
return (
<Wrap onClick={() => update()}>
<CTA>
<SVG src="/assets/svgs/btns/edit.svg" />
<span>Columns</span>
</CTA>
{active &&
<Dropdown>
<Head>
<span className="title">Edit Columns</span>
<span>Select the columns you would like to see</span>
</Head>
<Body>
{columnsData.map((item, i) => {
return (
<Section key={i}>
<SectionHead>
<span className="title">{item.title}</span>
<span>Select all</span>
</SectionHead>
<SectionList options={item.options}/>
</Section>
)
})}
</Body>
</Dropdown>
}
</Wrap>
)
}
const SectionList = ({options}) => {
const [selectedOptions, setSelectedOptions] = useState([0, 1, 2]);
const updateSelection = (z) => {
setSelectedOptions(selectedOptions.includes(z) ? selectedOptions.filter(j => j !== z) : [...selectedOptions, z]);
}
return (
<SectionListContainer>
{options.map((child, z) => {
const selected = selectedOptions.includes(z);
return (
<li key={z} className={classNames({selected})} onClick={() => updateSelection(z)}>
<span>{child.label}</span>
</li>
)
})}
</SectionListContainer>
)
}
I'm using a document structure like this
render() {
return (
<div className="MyComponent">
<ul className="">
{parseRecommendations(this.props.recommendations)}
</ul>
</div>
);
}
function parseRecomendations(recommendations) {
return recommendations.map((recommendation, key) => {
return (<Recommendation data={recommendation} key={key} />);
});
}
Where each Recommendation is its own component containing a checkbox
class Recommendation extends Component {
const recommendation = this.props.data;
const pageUrl = recommendation.url;
return (
<li className="article-item" key={key}>
<div className="article-info">
<input type="checkbox" defaultChecked="checked" aria-described-by={recommendation.pii} />
<a className="journal-title" href={pageUrl} id={recommendation.pii}>{recommendation.title}</a>
</div>
</li>
);
I'd like to have a title saying [Download (x) PDFs], where x is the number of selected checkboxes. How do I find the value of x in this case?
You need to store information about whether input is "checked" in your data. Then, simply count items with truthy "checked" flag.
Here is my solution. You should be able to get principle here and modify your code.
const data = [
{ checked: false, value: 'document 1' },
{ checked: true, value: 'document 2' },
{ checked: true, value: 'document 3' },
{ checked: false, value: 'document 4' },
{ checked: false, value: 'document 5' },
];
const Item = props => (
<div>
<input type="checkbox" checked={props.checked} onChange={props.onCheckChange} />
{ props.value }
</div>
)
var Hello = React.createClass({
getInitialState() {
return {
items: this.props.items.concat(),
};
},
onCheckChange(idx) {
return () => {
const items = this.state.items.concat();
items[idx].checked = !items[idx].checked;
this.setState({items});
}
},
totalChecked() {
return this.state.items.filter(props => props.checked).length;
},
render() {
return (
<div>
{ this.state.items.map((props, idx) => (
<Item {...props} key={idx} onCheckChange={this.onCheckChange(idx)} />
)) }
Total checked: { this.totalChecked() }
</div>
);
}
});
ReactDOM.render(
<Hello items={data} />,
document.getElementById('container')
);
If you just want to get the number of selected check-boxes you can try this
let checkedBoxes = document.querySelectorAll('input[name=chkBox]:checked');
Then get the total checked boxes via checkedBoxes.length
Edit:
Instead of querying whole document. You can get the nearest possible parent via getElementsByClassName or getElementById and then apply querySelectorAll on that element.
e.g
let elem = document.getElementsByClassName("MyComponent");
let checkedBoxes = elem.querySelectorAll('input[name=chkBox]:checked');
You also could obtain the total of selected checkboxes by element type. The "console.log(totalSelectedCheckboxes)" will print them when the state of totalSelectedCheckboxes change using useEffect Hook.
import React, { useState, useEffect } from 'react';
const RenderCheckboxes = () => {
const [totalSelectedCheckboxes, setTotalSelectedCheckboxes] = useState(0);
function handleChk() {
setTotalSelectedCheckboxes(document.querySelectorAll('input[type=checkbox]:checked').length);
}
useEffect(() => {
console.log(totalSelectedCheckboxes);
}, [totalSelectedCheckboxes]);
return (<div>
<div>
<input type="checkbox" value={1} onChange={() => handleChk()} />Chk1
</div>
<div>
<input type="checkbox" value={2} onChange={() => handleChk()} />Chk2
</div>
<div>
<input type="checkbox" value={2} onChange={() => handleChk()} />Chk2
</div>
</div>);
}
export default RenderCheckboxes;