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!
Related
I have a problem with my ReactJS App. I want to get values of checked radio buttons and after selecting I want to display the values of selected radio buttons.
The form is generated from a json file
[
{
variantId: 1,
variantName: 'Size',
variantOptions: [
{
variantOptionId: 1,
variantOptionName: 'S',
variantOptionPriceChange: 4.5
},
{
variantOptionId: 2,
variantOptionName: 'M',
variantOptionPriceChange: 4.5
},
]
},
{
variantId: 2,
variantName: 'Color',
variantOptions: [
{
variantOptionId: 3,
variantOptionName: 'Red',
variantOptionPriceChange: 4.5
},
{
variantOptionId: 4,
variantOptionName: 'Blue',
variantOptionPriceChange: 4.5
},
]
}
]
Demo of the problem is visible here: https://codesandbox.io/s/epic-http-bgmx3?file=/src/App.js
I want to display all selected items, not only the last one.
The problem is in this part of code, but I dont know how to rewrite it to achieve the desired behavior.
const addOption = (o) => {
setOptions({
optionId: o.variantOptionId,
optionName: o.variantOptionName,
optionPriceChange: o.variantOptionPriceChange
});
};
Thank you for your help, hope I described it clearly.
Simplest solution would be creating an object in useState with props keys for each variant and then store the selected option of that variant in related object prop
It should work like this:
export default function App() {
const [options, setOptions] = useState({});
const addOption = (name, o) => {
setOptions({ ...options, [name]: o });
};
return (
<div className="App">
{variants.map((variant, index) => {
return (
<Variant
options={options}
variant={variant}
addOption={addOption}
key={index}
/>
);
})}
<h3>
Selected variants are:
<ul>
{Object.keys(options).map((name, i) => {
return (
<li key={i}>
{name}: {options[name].variantOptionName}
</li>
);
})}
</ul>
</h3>
</div>
);
}
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} />
}
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 want to pass the function from stateful component to stateless component below is my code.
below is stateless code
const ProductsGridItem = props => {
const { result } = props;
const source = result._source;
return (
<ProductCard
ProductName={source.productName}
ProductGuid={source.productGuid}
Key={source.productGuid}
ProductStatus={source.status}
DecimalPrecision={decimalValue}
IsActive={source.isActive}
Image={source.imageName}
ProductCode={source.productCode}
MinPrice={source.minPrice}
Ratings={source.ratings}
CurrencySymbol={source.currencySymbol}
SupplierGuid={source.supplierGuid}
Type="grid"
ListBucketDetails={basketDetails}
WishListDetails={wishListDetails}
CompanyName={source.companyName}
BuyingWindowStatus={source["buyingwindowstatus.raw"]}
NewArrival={source["newarrival_raw.raw"]}
/>
);
};
and below this my class method starts that is stateful code starts.
class ProductListingPage extends Component {
constructor(props) {
super(props);
this.state = {
resources: [],
isFeatureAvailable: false,
loading: false,
decimalPrecesion: "",
filterList: [],
productBucketList: [],
open: false,
rating: 1,
companyGuid: null,
showMobileFilter: false,
dataEmpty: false
};
}
handleDrawerOpen = () => {
this.setState({ open: true });
};
}
I want to pass the handleDrawerOpen in ProductCard component. Could you please help how to do this?
I can fix this issue by moving const ProductsGridItem in class but my seniors not allowing me to do this. I dont know why. Both code are in same file. Please help.
EDITED:
In render the stateless component is using like below
<ViewSwitcherHits
hitsPerPage={16}
sourceFilter={[
"productName",
"productCode",
"imageName",
"manufacturerName",
"productGuid",
"tagAttributes",
"status",
"isActive",
"minPrice",
"ratings",
"currencySymbol",
"supplierGuid",
"companyName",
"buyingwindowstatus.raw",
"listproductsubcategory",
"newarrival_raw.raw"
]}
hitComponents={[
{
key: "grid",
title: getLabelText(
resources.filter(x => {
return x.resourceKey === "grid";
})[0],
"Grid"
),
itemComponent: ProductsGridItem,
InitialLoaderComponent: InitialLoaderComponent,
defaultOption: true
},
{
key: "list",
title: getLabelText(
resources.filter(x => {
return x.resourceKey === "list";
})[0],
"List"
),
itemComponent: ProductsListItem,
InitialLoaderComponent: InitialLoaderComponent
}
]}
scrollTo="body"
/>
In Hitcomponents - itemComponent: ProductsGridItem, I'm using Searchkit ViewSwitcherHits
I assume ProductListingPage's render (which you haven't shown) uses ProductsGridItem. In that location, you'd pass this.handleOpen:
<ProductsGridItem handleOpen={this.handleOpen} YourOtherStuffHere />
Within ProductsGridItem, you'd pass that on to ProductsGrid:
return (
<ProductCard
handleOpen={props.handleOpen}
YourOtherStuffHere
/>
);
Some style rules suggest not using props. within the JSX for child components. If your in-house style rules say not to do that, you can put handleOpen in an initial destructuring of props:
const ProductsGridItem = props => {
const { result: {_source: source}, handleOpen } = props;
return (
<ProductCard
handleOpen={handleOpen}
ProductName={source.productName}
ProductGuid={source.productGuid}
Key={source.productGuid}
ProductStatus={source.status}
DecimalPrecision={decimalValue}
IsActive={source.isActive}
Image={source.imageName}
ProductCode={source.productCode}
MinPrice={source.minPrice}
Ratings={source.ratings}
CurrencySymbol={source.currencySymbol}
SupplierGuid={source.supplierGuid}
Type="grid"
ListBucketDetails={basketDetails}
WishListDetails={wishListDetails}
CompanyName={source.companyName}
BuyingWindowStatus={source["buyingwindowstatus.raw"]}
NewArrival={source["newarrival_raw.raw"]}
/>
);
};
i am planning to build a generic filter like Gbif Have.
My question is how to approach this problem.
I like to use ReactJs for this project.
What other technology i need to look into along with React and redux in order to design such a generic filter.
I try to design this filter using React and redux only.
In my approach, i try to maintain the query parameter inside the state variable of the get_data method, in which i am fetching the data from the server. As somebody click on any filter button, then i pass custom event from that filter component along with query parameter and handle this event in get_data method. In get_data method again i am saving this value in get_data state parameter and again getting the new filtered data.
Now the Problem with above approach is that as the number of parameter increases it become very difficult to maintain.
my get_data constructor look like this.
constructor(props){
super(props);
this.state={
params:{
max:10,
offset:0,
taxon:[],
sGroup:[],
classification:undefined,
userGroupList:[],
isFlagged:undefined,
speciesName:undefined,
isMediaFilter:undefined,
sort:"lastRevised",
webaddress:""
},
title:[],
groupName:[],
userGroupName:[],
view:1
}
this.props.fetchObservations(this.state.params)
this.loadMore=this.loadMore.bind(this);
};
The way i am getting data from filter component is something like this.
this is my handleInput method which fire onSelect method from one of the filter.
handleInput(value,groupName){
this.setState({
active:true
})
this.props.ClearObservationPage();
var event = new CustomEvent("sGroup-filter",{ "detail":{
sGroup:value,
groupName:groupName
}
});
document.dispatchEvent(event);
};
the way i am handling this event in my get_data component is look something like this.
sGroupFilterEventListner(e){
const params=this.state.params;
if(!params.sGroup){
params.sGroup=[];
}
console.log("params.sGroup",params.taxon)
params.sGroup.push(e.detail.sGroup)
params.sGroup=_.uniqBy(params.sGroup)
const groupName=this.state.groupName;
var titleobject={};
titleobject.sGroup=e.detail.sGroup;
titleobject.groupName=e.detail.groupName;
groupName.push(titleobject);
let newgroupname=_.uniqBy(groupName,"sGroup")
params.classification=params.classification;
let isFlagged=params.isFlagged;
let speciesName=params.speciesName;
let MediaFilter=params.isMediaFilter;
let taxonparams=params.taxon;
taxonparams= taxonparams.join(",");
let sGroupParams=params.sGroup;
sGroupParams=sGroupParams.join(",");
let userGroupParams=params.userGroupList;
userGroupParams=userGroupParams.join(",");
let newparams={
max:10,
sGroup:sGroupParams,
classification:params.classification,
offset:0,
taxon:taxonparams,
userGroupList:userGroupParams,
isFlagged:isFlagged,
speciesName:speciesName,
isMediaFilter:MediaFilter,
sort:params.sort
}
this.props.fetchObservations(newparams);
this.setState({
params:{
max:10,
sGroup:params.sGroup,
classification:params.classification,
offset:0,
taxon:params.taxon,
userGroupList:params.userGroupList,
isFlagged:isFlagged,
speciesName:speciesName,
isMediaFilter:MediaFilter,
sort:params.sort
},
groupName:newgroupname
})
}
I registered and unRegistered the sGroupFilterEventListner in my componentDidMount and componentunmount method.
Presently i am also not considering the case where if somebody type in url bar, the filter panel change automatically.
Please consider all the above scenario and suggest me a generic way to do the same. thanks.
My Current Filter Panle look like this
Here's a quick example (React only, no Redux) I whipped up with a dynamic number of filters (defined in the filters array, but naturally you can acquire that from wherever).
const filters = [
{ id: "name", title: "Name", type: "string" },
{
id: "color",
title: "Color",
type: "choice",
choices: ["blue", "orange"],
},
{
id: "height",
title: "Height",
type: "choice",
choices: ["tiny", "small", "big", "huge"],
},
{
id: "width",
title: "Width",
type: "choice",
choices: ["tiny", "small", "big", "huge"],
},
];
const filterComponents = {
string: ({ filter, onChange, value }) => (
<input
value={value || ""}
onInput={e => onChange(filter.id, e.target.value)}
/>
),
choice: ({ filter, onChange, value }) => (
<select
value={value || ""}
onInput={e => onChange(filter.id, e.target.value)}
size={1 + filter.choices.length}
>
<option value="">(none)</option>
{filter.choices.map(c => (
<option value={c} key={c}>
{c}
</option>
))}
</select>
),
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = { filters: {} };
this.onChangeFilter = this.onChangeFilter.bind(this);
}
onChangeFilter(filterId, value) {
const newFilterState = Object.assign({}, this.state.filters, {
[filterId]: value || undefined,
});
this.setState({ filters: newFilterState });
}
renderFilter(f) {
const Component = filterComponents[f.type];
return (
<div key={f.id}>
<b>{f.title}</b>
<Component
filter={f}
value={this.state.filters[f.id]}
onChange={this.onChangeFilter}
/>
</div>
);
}
render() {
return (
<table>
<tbody>
<tr>
<td>{filters.map(f => this.renderFilter(f))}</td>
<td>Filters: {JSON.stringify(this.state.filters)}</td>
</tr>
</tbody>
</table>
);
}
}
ReactDOM.render(<App />, document.querySelector("main"));
body {
font: 12pt sans-serif;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<main/>
(originally on https://codepen.io/akx/pen/JyemQQ?editors=0010)
Hope this helps you along.