Sorting a ReactTable based on a Switch being active - reactjs

I need to sort a react table with the rows where a Switch is checked appearing at the top.
Here is the code I have so far.
import React from 'react';
import ReactTable from 'react-table';
import { useDispatch } from 'react-redux';
import { Switch } from '#material-ui/core';
import * as Actions from 'app/routes/actions/store/actions';
import { Pagination, FilterCaseInsensitive } from 'app/library';
export const PromotionControl = (props) => {
const dispatch = useDispatch();
const model = props.model;
const setModel = props.setModel;
const pageState = props.pageState;
const promotions = props.promotions;
function hasPromotion(promotion) {
if (model.actionPromotions === undefined)
return false;
const exists = model.actionPromotions.find(t => t.clientPromotionId === promotion.clientPromotionId);
return exists !== undefined;
}
function handlePromotionSelection(promotion) {
let selectedPromotions;
if (hasPromotion(promotion)) {
selectedPromotions = model.actionPromotions.filter(t =>
t.clientPromotionId !== promotion.clientPromotionId
);
}
else {
selectedPromotions = [...model.actionPromotions, {
clientPromotionId: promotion.clientPromotionId,
promotionName: promotion.promotionName
}];
}
setModel(values => ({ ...values, actionPromotions: selectedPromotions }));
}
return props.promotions.length <= 0 ? (<h5>No promotions configured.</h5>) : (
<React.Fragment>
<ReactTable
data={promotions}
PaginationComponent={Pagination}
defaultPageSize={5}
className="-striped -highlight"
minRows={0}
filterable={pageState.filterable}
sortable
filtered={pageState.filtered}
defaultFilterMethod={FilterCaseInsensitive}
onFilteredChange={(filtered) => dispatch(Actions.filterPromotions(filtered))}
columns={[
{
Header: "Client Promotion ID",
accessor: 'clientPromotionId',
width:200
},
{
Header: "Type",
accessor: 'promotionType',
maxWidth: 300
},
{
Header: "Name",
accessor: "promotionName"
},
{
maxWidth: 70,
style: { justifyContent: 'center' },
Cell: item => {
return (
<Switch
checked={hasPromotion(item.original)}
onClick={() => handlePromotionSelection(item.original)}
/>
)
}
}
]}
/>
</React.Fragment>
);
};
The column I want to sort the table by is the last column. At the moment the switch is checked based on the result of a check performed by the hasPromotion function. The issue is I don't know how to sort the whole table based on this column with the checked ones appearing at the top of the table.
Any ideas would be greatly appreciated.

As in Javascript, we have :
true - false === 1
false - true === -1
You could just base your sort on these values :
yourDataMatrix = yourDataMatrix.sort((a,b) => b.values[indexOfColumnToSort] - a.values[indexOfColumnToSort]);
Since I don't know the structure of your data, this code is not working, but you can use the logic in your code

Related

Material-UI v5 DataGridPro Highlight First Row Upon Load

I am loading a really basic grid using DataGridPro and I need the top row to be selected and highlighted upon loading. I did not see a great example when I tried researching.
This is what my DataGridPro component looks like:
Here is what I have so far:
<DataGridPro
disableMultipleSelection={true}
columns={[
{
field: "startDate",
headerName: "Start Date",
description: "start.",
type: "date",
valueFormatter: ({ value }) => dateFormatter(value),
flex: 0.5,
},
{
field: "endDate",
headerName: "End Date",
description: "end.",
type: "date",
valueFormatter: ({ value }) => (value !== null ? dateFormatter(value) : null),
flex: 0.5,
},
]}
rows={data ? data : null}
></DataGridPro>
I'm not sure what to do since I can't find any examples in their demos or API documentation.
The example that gets you closest to what you want is the Controlled selection example. That example demonstrates how to hold the selection in state and pass it as a prop to the data grid. The example does not include how to change the selection from outside the data grid.
The main thing you need to know is that the selectionModel prop is an array of the selected row ids, so to have the first row start as selected, you need to pass in an array with that row id.
Below is a modified version of the example from the documentation that demonstrates selecting the first row.
import * as React from "react";
import { DataGrid } from "#mui/x-data-grid";
import { useDemoData } from "#mui/x-data-grid-generator";
export default function ControlledSelectionGrid() {
const { data, loading } = useDemoData({
dataSet: "Commodity",
rowLength: 10,
maxColumns: 6
});
const [selectionModel, setSelectionModel] = React.useState([]);
const dataRef = React.useRef(data);
React.useEffect(() => {
// The ref allows me to leave `data` out of the dependency array
// of the next effect, so that it is only triggered by changes
// to the `loading` state.
dataRef.current = data;
});
React.useEffect(() => {
if (!loading) {
const { rows } = dataRef.current;
if (rows.length > 0) {
setSelectionModel([rows[0].id]);
}
}
}, [loading]);
return (
<div style={{ height: 400, width: "100%" }}>
<DataGrid
checkboxSelection
onSelectionModelChange={(newSelectionModel) => {
setSelectionModel(newSelectionModel);
}}
selectionModel={selectionModel}
{...data}
/>
</div>
);
}
There is some extra complexity in the above code due to useDemoData loading the data asynchronously. Depending on how your data is passed to the data grid, you may be able to avoid the useEffect calls and simplify this to something like the following:
import * as React from "react";
import { DataGrid } from "#mui/x-data-grid";
export default function ControlledSelectionGrid({ data }) {
const [selectionModel, setSelectionModel] = React.useState(() => {
const { rows } = data;
const initialSelectionModel = [];
if (rows.length > 0) {
initialSelectionModel.push(rows[0].id);
}
return initialSelectionModel;
});
return (
<div style={{ height: 400, width: "100%" }}>
<DataGrid
checkboxSelection
onSelectionModelChange={(newSelectionModel) => {
setSelectionModel(newSelectionModel);
}}
selectionModel={selectionModel}
{...data}
/>
</div>
);
}

DataGrid returns only 1 result instead of more

I'm trying to display all the results on this data table from MUI but it is only displaying 1 result instead of all and I don't see what I'm doing wrong.
If you have good ideas and best practices on how to do the table would I would be very appreciated.
import React, { FC } from "react";
import { DataGrid, GridColDef, GridRowsProp } from "#mui/x-data-grid";
interface Props {
processes: any;
}
const DataTable: FC<Props> = ({ processes }) => {
const inCourse = processes.filter(function (process: any) {
return process.isActive === 0;
});
const columns: GridColDef[] = [
{ field: "id", headerName: "Id", hide: true },
{ field: "NProc", headerName: "N/Proc" },
{ field: "VRef", headerName: "V/Proc" },
];
const rows: GridRowsProp = [
{
id: inCourse.map((process: any) => {
return process.id;
}),
VRef: inCourse.map((process: any) => {
return process.houseType;
}),
NProc: inCourse.map((process: any) => {
return process.processNumber;
}),
},
];
return (
<div className="gridboard">
<DataGrid rows={rows} columns={columns} pageSize={5} checkboxSelection />
</div>
);
};
export default DataTable;
The rows array contains only one value so DataGrid display one row, you probably want something like this. See Array.map() for more detail:
const rows: GridRowsProp = inCourse.map(c => ({
id: c.id,
VRef: c.houseType,
NProc: c.processNumber,
}));

Material-UI dataGrid with nested data from redux

I am developing a page that fetches data from an API and assembles a table using Material UI and Datagrid. My goal to parse data into the Table. My data looks like this
{
id: 1,
device_mrid: "xxx1",
canaryDeviceId: "xxx",
},
{
id: 2,
device_mrid: "xxx2",
canaryDeviceId: "xxx",
},
{
id: 3,
device_mrid: "xxx3",
canaryDeviceId: "xxx",
},
I was able to create a dataGrid table using fake API and the want to get the end result like this https://codesandbox.io/s/bold-leakey-eu3cq?file=/src/App.js
I use the redux state metersList.metersData.data to save meters data.
My code is Looking like this:
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { readWaterMeter } from "./components/actions/waterActions";
import { FormattedMessage } from "react-intl";
import { DataGrid } from "#material-ui/data-grid";
export default function WaterNew() {
const dispatch = useDispatch();
const metersList = useSelector((state) => state.waterReducer);
useEffect(() => {
dispatch(readWaterMeter());
}, [dispatch]);
let rows = [];
rows =
metersList.metersData.data &&
metersList.metersData.data.length > 0
? metersList.metersData.data.map((obj, index) => {
return (rows = {
id: index,
device_mrid: obj.device_mrid,
canaryDeviceId: obj.canaryDeviceId
});
})
: " ";
const columns = [
{
field: "device_mrid",
headerName: "device_mrid",
flex: 1,
renderCell: ({ value }) => <FormattedMessage id={value} />
},
{
field: "canaryDeviceId",
headerName: "canaryDeviceId",
flex: 1,
renderCell: ({ value }) => <FormattedMessage id={value} />
}
];
return (
<div className="App" style={{ height: "100%", width: "100%" }}>
<DataGrid rows={rows} columns={columns} />
{console.log("metersList", metersList.metersData.data)}
</div>
);
}
I am able to see my meters data in console.log(return) but not able to map.
Initially I got this error:
Uncaught (in promise) TypeError: Cannot read property 'length' of undefined
After that I have added length property then I am getting another error like this
Uncaught (in promise) TypeError: e.forEach is not a function
Now I don't know how to proceed....
And the second issue is I am repeating the renderCell option in Columns, is there any better way to write the renderCell to reduce the lines of code?
I really appreciate the help.
After adding an empty array to the useSelector, it worked and also I removed length method and ternary operator from rows.
const metersList = useSelector(
(state) => state.waterReducer.metersData.data || []
);
let rows = [];
rows = metersList.map((obj, index) => {
return (rows = {
id: index,
device_mrid: obj.device_mrid,
canaryDeviceId: obj.canaryDeviceId,
});
});
I have similar error "TypeError: Cannot read property 'length' of undefined", but with React Query as data source. Solved it by checking data state, if it "isLoading" - display empty array.
const { isLoading, error, data } = useQuery([apiUrl], () =>
fetch(apiUrl).then((res) => res.json())
);
<DataGrid
rows={!isLoading ? data : []}
columns={columns}
pageSize={100}
rowsPerPageOptions={[50]}
checkboxSelection
disableSelectionOnClick
experimentalFeatures={{ newEditingApi: true }}
components={{ Toolbar: GridToolbar }}
/>;

Edit custom column component while adding new row of Material Table

With the React Material Table library, is it possible to render a custom component while adding a new row? I'm using a custom component (a Material UI select box, actually), for the Expected Result column. When I add a new row, I only see a field for the Requirement column, not the Expected Result column. Is it possible to add an input for the Expected Result column of the new row as well?
Another option is to not use custom components at all and instead use something like the Cell Editable Example of https://material-table.com/#/docs/features/editable. However, I'm not a fan of the extra clicks that it takes to edit the Expected Result, compared to directly using a Select field.
import MaterialTable from 'material-table'
import { MenuItem, Select } from '#material-ui/core'
import React, { useState } from 'react'
import update from 'immutability-helper'
type PassFailNA = 'Pass' | 'Fail' | 'N/A'
type RowData = {
requirementId: number,
requirementName: string,
expectedResult: PassFailNA,
expectedResultId?: number
}
export function ExpectedResultsTable(props: {
scenarioId: number
}) {
const [tableData, setTableData] = useState<RowData[]>([{ requirementId: 1, requirementName: 'hello', expectedResult: 'Pass' }])
const { enqueueSnackbar } = useSnackbar()
const handleSelect = (id: number) => (event: React.ChangeEvent<{ name?: string; value: any }>) => {
setTableData((tableData: RowData[]) => {
const rowNum = tableData.findIndex(x => x.requirementId === id)
return update<RowData[]>(tableData, {
[rowNum]: { expectedResult: { $set: event.target.value } }
})
})
}
return (
<MaterialTable<RowData>
title=""
columns={[
{
title: 'Requirement',
field: 'requirementName'
},
{
title: 'Expected Result',
field: 'expectedResult',
render: (rowData) => (
<Select value={rowData.expectedResult} onChange={handleSelect(rowData.requirementId)}>
<MenuItem value="Pass">Pass</MenuItem>
<MenuItem value="Fail">Fail</MenuItem>
<MenuItem value="N/A">N/A</MenuItem>
</Select>
)
}
]}
data={tableData}
editable={{
onRowAdd: newRow =>
new Promise((resolve, reject) => {
setTimeout(() => {
setTableData(tableData => update(tableData, { $push: [{ ...newRow, expectedResult: 'N/A'}] }))
resolve()
}, 1000)
})
}}
/>
)
}
To achieve what you are looking for, I think you should specify the editComponent property ( besides render ) when defining the column. That prop takes a function where you can define the component used during the edit or creation phase.
Here is an example I made with a boolean input:
const tableColumns = [
{ title: "Client", field: "id" },
{ title: "Name", field: "name" },
{
title: "booleanValue",
field: "booleanValue",
editComponent: (props) => {
console.log(props);
return (
<input
type="checkbox"
checked={props.value}
onChange={(e) => props.onChange(e.target.checked)}
/>
);
},
render: (rowdata) => (
<input type="checkbox" checked={rowdata.booleanValue} />
)
}
];
Link to working sandbox. I hope that works for you!

Checkbox for specific row in react-table?

import React, { Component } from 'react';
import { connect } from 'react-redux';
import getSchoolsList from '../Actions/Index';
import ReactTable from "react-table";
import checkboxHOC from "react-table/lib/hoc/selectTable";
import "react-table/react-table.css";
const CheckboxTable = checkboxHOC(ReactTable);
class Home extends Component {
constructor(props){
super(props);
this.state = {
selection: [],
selectAll: false
};
}
componentDidMount(){
this.props.getSchoolsList();
}
toggleSelection = (key, shift, row) => {
let selection = [...this.state.selection];
const keyIndex = selection.indexOf(key);
if (keyIndex >= 0) {
selection = [
...selection.slice(0, keyIndex),
...selection.slice(keyIndex + 1)
];
} else {
selection.push(key);
}
this.setState({ selection });
};
toggleAll = () => {
const selectAll = this.state.selectAll ? false : true;
const selection = [];
if (selectAll) {
const wrappedInstance = this.checkboxTable.getWrappedInstance();
const currentRecords = wrappedInstance.getResolvedState().sortedData;
currentRecords.forEach(item => {
selection.push(item._original._id);
});
}
this.setState({ selectAll, selection });
};
isSelected = key => {
console.log(key);
return this.state.selection.includes(key);
};
logSelection = () => {
console.log("selection:", this.state.selection);
};
render() {
const { toggleSelection, toggleAll, isSelected, logSelection } = this;
const { selectAll } = this.state;
const checkboxProps = {
selectAll,
isSelected,
toggleSelection,
toggleAll,
selectType: "checkbox",
};
const data = this.props.StateData?this.props.StateData.data:[];
const {loading, StateData} = this.props;
if (loading) {
{console.log(loading)}
return <div>Loading...</div>;
}
return (
<div>
{console.log(this.checkboxTable)}
<button onClick={logSelection}>Log Selection</button>
<CheckboxTable
ref={r => (this.checkboxTable = r)}
data={data}
columns={[
{
Header: "School Name",
accessor: "name"
},
{
Header: "Location",
id: "lastName",
accessor: d => d.area + ',' + d.city
},
{
Header: "Curriculum",
accessor: "curriculum"
},
{
Header: "Grade",
accessor:"grade"
},
{
Header: "Web App_URL",
accessor: "webapp_url",
},
{
Header: "Status",
id: "status",
accessor: d =>{
if(d.publish === true){
console.log(d.publish)
return 'Publish';
}else{
return 'Unpublished'
}
}
}
]}
defaultPageSize={10}
className="-striped -highlight"
{...checkboxProps}
/>
</div>
);
}
}
function mapStateToProps (state) {
return {
StateData:state.login.schools,
loading: state.login.loading,
}
};
export default connect(mapStateToProps, {getSchoolsList})(Home);
Hi all, can someone help me with this what is the wrong i am not getting individual checkboxes in this ? i checked this link code in my local it is working <https://codesandbox.io/s/7yq5ylw09j?from-embed>, but whenever i add my dynamic data it is not working.
Hi all, can someone help me with this what is the wrong i am not getting individual checkboxes in this ? i checked this link code in my local it is working <https://codesandbox.io/s/7yq5ylw09j?from-embed>, but whenever i add my dynamic data it is not working.
Hi all, can someone help me with this what is the wrong i am not getting individual checkboxes in this ? i checked this link code in my local it is working https://codesandbox.io/s/7yq5ylw09j?from-embed, but whenever i add my dynamic data it is not working.
If your using TypeScript and tslint this happens via the example for select table(checkboxes) getdata() does this:
const _id = chance.guid();
return {
_id,
...item
};
tslint complains about the _id var naming with "variable name must be in lowerCamelCase, PascalCase or UPPER_CASE"
You can see that at: https://react-table.js.org/#/story/select-table-hoc
So you have to change _id to id if you want to get past tslint. Changing from _id to id breaks the default keyField logic in react-table which wants _id. That necessitates setting the keyField property to "id".
If you do not mention unique key id by default it will take "_id" as the key field. By defining a key value you can overcome the above mentioned matter as follows.
Let's say there is a specific column named "USER ID". And we'll take the accessor of the column as "uid".
The code should be modified as follows.
Checkbox Table
<CheckboxTable
keyField="uid"
......Rest of your code....
/>
toggleAll()
toggleAll() {
..........code...........
currentRecords.forEach(item => {
selection.push(item.uid);
});
}
.......code............
}

Resources