I have a datagrid using MUI and I have came across a use case where I need to hide one of the columns if I do not have a certain role. Here is the code.
const hideColumn = () => {
const globalAdmin = auth.verifyRole(Roles.Admin);
if(!globalAdmin){
return true;
}
return false;
};
const columns = [
{ field: 'id', headerName: 'ID', width: 100 },
{ field: 'name',
headerName: 'Client code',
flex: 1,
hide: hideColumn,
renderCell: (params) => {
return params.getValue("name");
},
},
];
I'm confused on why this is not working. If I just use hide:true or hide:false it works but I need have an if statement to check the credentials first and this can't be done in the renderCell (or at least I can't get it to work). Does anyone know how to do this correctly?
You should call it as function hide: hideColumn()
Or you can create a function to retrieve your columns and pass props to this function. Then you just have to push your columns according to criteria:
export default function getColumns (product, isHide, t) {
let columns = [
{
field: 'organizationName',
headerName: headerNameOrganization,
flex: 0.3,
},
]
if (!product) {
columns.push(
{
field: 'status',
headerName: headerNameStatus,
flex: 0.2,
filterable: false,
})
}
return columns
}
Use the prop columnVisibilityModel of <DataGrid /> to control the column visibility dynamically.
The prop can also have a listener.
You can use the onColumnVisibilityModelChange prop to listen to the changes to the visible columns and update the prop accordingly.
(MUI doc)
At first, I was using the initialState (as the ff), but MUI said in their documentation:
The initialState can only be used to set the initial value of the state, the grid will not react if you change the initialState value later on.
If you need to fully control specific models, use the control props instead...
<DataGrid
columns={columns}
rows={rows}
loading={!users.length}
initialState={{
columns: {
columnVisibilityModel: {
myColumn1: isAdmin(user),
myColumn2: isAdmin(user),
},
},
}}
/>
Related
We are using MUI DataGrid in our React based project.
At the moment I am trying to save/persist state of columns after toggling visibility of some columns with DataGrid's toolbar column menu, as currently after re-render it is back to default column setup.
I would like to know how could I access state of DataGrid/visibility state of columns in DataGrid so I can adjust/save it/reuse it later?
So far I meddled a bit with a apiRef, but all I got from apiRef.current was empty object. I am adding below some basic codeSandbox example to show how I tried to access it.
https://codesandbox.io/s/datagridprodemo-material-demo-forked-189j9?file=/demo.js
Maybe there is better/different approach, or I just need to create the state somehow. We would like to persist the state of the columns as user preference possibly in a future so this is vital for that to happen.
All suggestions are welcome and I thank you beforehand.
Fortunately, the DataGrid API provides the columnVisibilityModel and onColumnVisibilityChange props.
See this code sandbox for a simple example of controlling the columnVisibilityModel: https://codesandbox.io/s/mui-datagrid-oncolumnvisibilitychange-savestate-u1opzc?file=/src/App.tsx:1960-1984
Here is the code for a simple implementation. Your initial state may vary. Also, note that I could not figure out how to get DataGridPro to call onColumnVisibilityChange unless columnVisibilityModel was initially undefined. Bug, or my mistake, I am uncertain.
import "./styles.css";
import React from "react";
import {
DataGrid,
GridRowsProp,
GridColDef,
GridCallbackDetails,
MuiEvent,
GridColumnVisibilityModel,
GridColumnVisibilityChangeParams
} from "#mui/x-data-grid";
import { Button } from "#mui/material";
const rows: GridRowsProp = [
{ id: 1, col1: "Hello", col2: "World" },
{ id: 2, col1: "DataGridPro", col2: "is Awesome" },
{ id: 3, col1: "MUI", col2: "is Amazing" }
];
const columns: GridColDef[] = [
{ field: "col1", headerName: "Column 1", width: 150 },
{ field: "col2", headerName: "Column 2", width: 150 }
];
const initialVisibilityModel = { col1: true, col2: true };
export default function App() {
// it is strange, but in order for DataGridPro to call onColumnVisibilityChange, columnVisibilityModel must be undefined initially
const [
currentGridColumnVisibilityModel,
setCurrentGridColumnVisibilityModel
] = React.useState<GridColumnVisibilityModel | undefined>(undefined);
const [mySavedValue, setMySavedValue] = React.useState<
GridColumnVisibilityModel | undefined
>(undefined);
const onColumnVisibilityChange = React.useCallback(
(
params: GridColumnVisibilityChangeParams,
event: MuiEvent<{}>,
details: GridCallbackDetails
): void => {
console.log("params", params);
setCurrentGridColumnVisibilityModel((s) => ({
// per the DataGridPro strangeness, we must marry in initial state only the first update
...(s ? s : initialVisibilityModel),
[params.field]: params.isVisible
}));
},
[]
);
const saveACopyOfGridState = () => {
setMySavedValue(currentGridColumnVisibilityModel || initialVisibilityModel);
};
const loadSavedCopyOfGridState = () => {
setCurrentGridColumnVisibilityModel(mySavedValue || initialVisibilityModel);
};
const currentVisibilityAsText =
`${Object.keys(currentGridColumnVisibilityModel ?? {}).map(
(key) => `{${key}:${currentGridColumnVisibilityModel?.[key]}}`
)}` || "empty";
const savedVisibilityAsText =
`${Object.keys(mySavedValue ?? {}).map(
(key) => `{${key}:${mySavedValue?.[key]}}`
)}` || "empty";
return (
<div style={{ height: 300, width: "100%" }}>
<DataGrid
rows={rows}
columns={columns}
columnVisibilityModel={currentGridColumnVisibilityModel}
onColumnVisibilityChange={onColumnVisibilityChange}
/>
<div>
<Button onClick={saveACopyOfGridState} variant="contained">
SAVE CURRENT COLUMN VISIBILITY STATE
</Button>
<Button
onClick={loadSavedCopyOfGridState}
variant="contained"
color="warning"
>
LOAD SAVED COLUMN VISIBILITY STATE
</Button>
</div>
<p>Current filter state: {currentVisibilityAsText}</p>
<p>Saved filter state: {savedVisibilityAsText}</p>
</div>
);
}
I made a hook to persist column settings to localStorage. It uses callbacks on the API ref object. Usage:
function MyGrid() {
const apiRef = useGridApiRef()
usePersistColumnSettings(apiRef, 'customers-grid')
return <DataGrid apiRef={apiRef} />
}
Hi I'm a beginner in reactjs, I'm trying map array and insert the file into another array, and after insert, I map the file into the table, but I got Error "Maximum update depth exceeded"
This is my code
import React, { useEffect, useState } from "react";
import "../Components.css";
import { MDBDataTable } from "mdbreact";
import AuthService from "../../Services/AuthService";
// import AuthService from "../Services/AuthService";
export default function Dataadmin() {
const [Searchfile, setSearchfile] = useState([]);
const [data, setData] = useState({
columns: [
{
label: "No",
field: "no",
sort: "asc",
},
{
label: "Title",
field: "title",
sort: "asc",
},
{
label: "Singer",
field: "singer",
sort: "asc",
},
{
label: "Genre",
field: "genre",
sort: "asc",
},
{
label: "Country",
field: "country",
sort: "asc",
},
{
label: "Action",
field: "action",
sort: "asc",
},
],
rows: [],
});
AuthService.getalldata().then((res) => {
setSearchfile(res.data);
});
useEffect(() => {
Searchfile.map((item, index) => {
const cloned = { ...data };
cloned.rows.push({
no: index + 1,
title: item.title,
singer: item.singer,
genre: item.genre,
country: item.country,
action: (
<>
<button className="btn-action">
<i className="fas fa-pencil-alt"></i>
</button>
<button className="btn-action" style={{ marginLeft: "1vh" }}>
<i className="far fa-trash-alt"></i>
</button>
</>
),
});
setData(cloned);
});
}, [Searchfile, data]);
return (
<div className="div-admin">
<div className="table-adminss">
<MDBDataTable
className="mytable-admin"
striped
bordered
small
data={data}
/>
</div>
</div>
);
}
Can someone explain to me why I get an error and how to fix it? , hope you guys understand what I'm asking :D
There are two scenarios in react in which a component re-renders
When the state changes (In this case, an example is searchFile,)
When the props changes ( The properties passed to the component)
The function
AuthService.getalldata().then((res) => {
setSearchfile(res.data);
});
is called each time the component renders calling the setSearchfile. So once the setSearchfile is called the component re-renders once again calling the same function mentioned above(Authservice.getAllData()).This process repeats. So this will result in an infinite loop which the browser cannot handle. Hence you get the above error.
Moving the (Authservice.getAllData()) into the method of useEffect should solve the maximum update depth exceeded.
It causes because of you set setData inside a loop.
One you can do, you can store the array inside a const and setData(...data, *that const*) outside of the loop. you don't need loop here actually. As far I know MDDataTable map data itself.
according to your code you can simply do it,
useEffect(() => {
setData(...data, Searchfile) //if your Searchfile contains data
});
}, [Searchfile, data]);
Hope you got this.
I have three components in my project, App, DataTable and Table.
The App will render a DataTable that contains the Table component. I just called DataTable with data and columns props in it.
// data sample
const tmpData = [
{
name: "Can",
age: 4,
}, {
name: "David",
age: 44,
}, {
name: "Sara",
age: 14,
}, {
name: "Hani",
age: 24,
}
]
// main columns array
const tmpColumns = [
{
title: "Name",
accessor: "name",
}, {
title: "Age",
accessor: "age",
}
]
function App() {
return (
<div className="App" style={{background: "#f0f0f0", padding: "1rem"}}>
<div>App Component:</div>
<DataTable data={tmpData} columns={tmpColumns}/>
</div>
);
}
DataTable component is just for handling selection and filter actions on my table. So, it could manipulate the data and the columns. the Table will render in it and here is the code of DataTable.
function DataTable(props) {
const [data, setData] = useState(props.data)
const [columns, setColumns] = useState(props.columns)
const [selection, setSelection] = useState([])
useEffect(() => {
// add select columns after component mount
handleColumnChange()
}, [])
// listen to selection change
useEffect(() => {
// selection change log, It change on each select.
console.log(selection);
}, [selection])
function handleRowSelect(rowName) {
const keyIndex = selection.indexOf(rowName);
console.log(selection, rowName, keyIndex);
if (keyIndex === -1)
setSelection(preSelection => ([...preSelection, ...[rowName]]))
else
setSelection(preSelection => preSelection.filter(sl => sl !== rowName))
}
function handleColumnChange() {
// add select column if not added already
if (!columns.find(col => col.accessor === 'select')) {
setColumns([
...[{
title: "Select",
accessor: "select",
// this method will execute to render checkbox on Select table
Cell: function (row) {
return <input type="checkbox"
onChange={() => handleRowSelect(row.name, selection)}
checked={selection.includes(row.name)}/>
},
}],
...columns,
])
}
}
return (
<div className="DataTable" style={{background: "#e0e0e0", padding: "1rem"}}>
<div>Data Table:</div>
<Table {...{data, columns}}/>
</div>
)
}
Table component will render columns and suitable data for them. For each column in columns array we have an item to access data (accessor) or an executable method to return custom data (Cell) and here is its code.
function Table(props) {
const [data, setData] = useState(props.data)
return (
<div className="Table" style={{background: "#d5d5d5", padding: "1rem"}}>
<div>Table</div>
<table>
<tbody>
<tr>
{props.columns.map((th, key) => (
<th key={key}>{th.title}</th>
))}
</tr>
{/* generating data rows */}
{data.map((dr, key) => (
<tr key={key}>
{columns.map((col, index) => (
<td key={index}>
{
// the "Cell" method has high priority than "accessor" selector
(col.Cell && typeof col.Cell === "function") ?
col.Cell(dr) :
dr[col.accessor]
}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
}
As you saw above, to handle row selection I manipulate the columns in the DataTable component by adding a new column at first index of my columns array. Everything works fine for now. But, when I try to select a row, the Call methods of the select column, could not access the selection array state of my DataTable component. and it's my problem!
Actually, on each select, the selection array must update and target checkbox must check. Apparently, there is a copy of the selection that changes (or maybe not changes).
You also could check the whole project on CodeSandbox
I have some fixes to your code, check it out on CodeSandbox.
The idea is that you don't need to put the columns to state, instead you just need to get the columns with the selection box.
I also added a simple optimization to it by implementing React.useMemo to memoized the calculated columns. It will only be re-calculated when the props.columns and selection state changes.
Hope this helps! Happy coding! :)
Ok, your tmpData passed from App to DataTable is read-only. So by design you will not see any change your data along the way.
There're couple of ways to get it working, mostly having something to do to allow your DataTable to pass the change back to App if that happens.
Step 1, you could add one prop called onRowClick on the DataTable,
<DataTable data={tmpData} onRowClick={row => { console.log(row) } />
Step 2, you need to allow your tmpData to change after the event. You are using hooks, so we can
const [tmpData, setTmpData] = useState([sampleData])
return (
<DataTable data={tmpData} onRowClick={row => {
// use setTmpData to adjust the tmpData to get a new instance of tmpData
} />
)
Of course for things with this complexity, we normally use useReducer instead of useState, since there's definitely other things that you want to do other than selecting the row :)
I am writing a react application that is a thin client UI for a financial trading application. A core requirement is that the application be completely dynamic and configurable, including forms. Specifically, I have trade entry forms that need to be defined on the server side and stored in the database to be rendered dynamically on the client but the layout is important and needs to be able to support multiple different formats. I have seen a few libraries that take JSON form schemas and create static forms from them but none of them seem to support the kind of layout flexibility I need. For example, I need to support tabs, columns, and rows of components. My question is - can anyone suggest a ReactJs library that can do what I'm looking for? If not, how might I go about implementing it myself?
Here's a more concrete example; Suppose I have a schema fetched from the server via a REST call that looks something like this:
{
title: "Securities Finance Trade Entry",
children: [
{
containerType: "tabs",
children: [
{
title: "Common",
children: [
{
containerType: "row",
children: [
{
input: "ComboBox",
label: "Trade Type",
options: ["Repo", "Buy/Sell", "FeeBased"]
},
{
input: "ComboBox",
label: "Direction",
options: ["Loan", "Borrow"]
}
]
},
{
containerType: "row",
children: [
{
containerType: "column",
children: [
{
containerType: "row",
children: [
{
input: "text",
label: "Book"
},
{
input: "text",
label: "Counterparty"
}
]
},
{
containerType: "row",
children: [
{
input: "date",
label: "StartDate"
},
{
input: "date",
label: "EndDate"
}
]
},
{
containerType: "row",
children: [
{
input: "text",
label: "Security"
},
{
input: "numeric",
label: "Quantity"
}
]
}
]
}
]
}
]
}
]
}
]
}
Which I expect to render something like:
Basically in that schema only one tab would be shown, but there could be multiple tabs, each containing multiple children in rows and columns and potentially nested containers of tabs too. If I were to render this myself in react I would think about using .map to iterate through the json and a number of if statements to insert tags where appropriate. However, items need to be nested so I don't know how to render it such that the tag chosen is dynamic and it can have children... For example I could write:
{ if (container.containerType === "column") { () } but then I'd somehow need to embed the rest of the controls inside that tag, I don't think I could just emit a () at the end...
Another option I've considered is translating the above json to JSX on the server side and sending that. It would be fairly easy to write a parser on the Java server side that converts the above json to a JSX document and return that to the client, but then how would I render it? Is there any way I could do something like:
onComponentMount() {
fetch(webserviceUrl + 'getJsxForForm/' + props.formName)
.then(result => {this.setState({form : result});
}
render() {
return ({this.state.form});
}
But again, I don't think this will work. If I fetch the document from the server it would render it just as plain text and not actually convert it to valid html, right?
So, what are my options? I'm looking for suggestions of an existing library that would do this, or suggestions on either of the other two approaches that I'm mentioned (would they work?, how could I do it?), or alternative ideas.
Thanks,
Troy
I love the concept of dynamically rendering pages from some sort of JSON configuration.
The key will be defining Components to match containerTypes and inputs and then traversing your JSON configuration through a recursive function. In your JSON configuration, I suggest using component naming conventions wherever you want a component to be rendered. Hence, capitalizing Tabs, Row, Column, etc
Here is one example of that function. Note, in each of the containerType Components there is a call to this function with children being passed in.
See this pen: https://codepen.io/wesleylhandy/pen/oQaExK/
Example Component:
const Container = props => {
return (
<div className="container">
<h1>{props.title}</h1>
{renderChildren(props.children)}
</div>
)
}
Example Recursive-ish rendering of children
const renderChildren = children => {
return children ? children.map((child, ind) => {
const newChildren = child.children ? [...child.children] : [];
const {containerType, title, input, label, options} = child
let key;
if (newChildren.length) {
key = `${containerType}-${ind}`;
switch (containerType) {
case "Tabs":
return <Tabs
key={key}
title={title}
children={newChildren}
/>
case "Column":
return <Column
key={key}
title={title}
children={newChildren}
/>
case "Row":
return <Row
key={key}
title={title}
children={newChildren}
/>
default:
return <Common
key={key}
title={title}
children={newChildren}
/>
}
} else {
key=`${input}-${ind}`
switch (input) {
case "ComboBox":
return <SelectGroup
key={key}
label={label}
options={options}
/>
case "Text":
case "Date":
case "Numeric":
return <InputGroup
key={key}
label={label}
type={input}
/>
}
}
}) : null
}
I'm currently using React-Table and it's working great.
I want to have a dynamic pageSize according to the data filtered in the table.
<ReactTable
...
filtered={[{
"id": "stage",
"value": 1
}]}
getTdProps={(state, rowInfo, column, instance) => {
return {
onClick: () =>
this.setState({
preliminary:state.sortedData.length
})
};
}}
pageSize={this.state.preliminary}
/>
And my state in the constructor
this.state = {
preliminary: 10
};
This works great when clicking because of the onClick() event. I want it to fire onLoad() of the page.
Changing onClick() to onLoad() doesn't do anything.
Any help appreciated!
Adding an onLoad event to the getTdProps() method doesn't make much sense because td elements don't have an onload event.
Sounds like you'll want to make use of onFilteredChange to update this.state.preliminary to a new value, which will update the pageSize prop of the <ReactTable /> component.
Here's a simplified example. The table starts with a pageSize of 5. Inserting a filter of any kind into either column will change the pageSize to 10. This uses the onFilteredChanged prop. I imagine you would want to include some logic in the handleFilterChange function to set the pageSize to an appropriate value however.
class MyTable extends React.Component {
constructor(props){
super(props)
this.state = {
pageSize: 5,
filter: false
}
}
handleFilterChange = (column, value) => {
this.setState({
pageSize: 10
})
}
render() {
const data = [{
name: 'Tanner Linsley',
age: 26
},
{
name: 'Brett DeWoody',
age: 38
},
{
name: 'Santa Clause',
age: 564
}]
const columns = [{
Header: 'Name',
accessor: 'name' // String-based value accessors!
}, {
Header: 'Age',
accessor: 'age',
Cell: props => <span className = 'number'> {
props.value
} </span>
}]
return (
<div>
<ReactTable.default
data={data}
columns={columns}
filterable={true}
onFilteredChange={this.handleFilterChange}
pageSize={this.state.pageSize} />
</div>
)
}
}
ReactDOM.render(<MyTable /> , document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-table/6.7.5/react-table.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/react-table/6.7.5/react-table.css" rel="stylesheet" />
<div id="app"></div>
Following #Brett DeWoody advice, the solution was to find the length of the filtered data
For that, I used lodash
pageSize={_.filter(data.items, { 'stage': 1, 'status': 1 }).length}
Thanks!