Dynamic column and values in react table - reactjs

response : {initial_data: [{
"Did I see this plant in 2016?"=>"No",
"Did I see this plant in 2017?"=>"Yes",
"How Many?"=>1,
"User Data 4"=>"x",
"User Data 5"=>nil,
"Did I see this plant in 2022?"=>"No",
"Name"=>"Abronia alpina"},
{"Did I see this plant in 2016?"=>"No",
"Did I see this plant in 2017?"=>"No",
"How Many?"=>11,
"User Data 4"=>"x",
"User Data 5"=>nil,
"Did I see this plant in 2022?"=>"Yes",
"Name"=>"Abronia alpina1"}]
}
Based on above response I am executing below code to print the header and there values dynamically.
const CUSTOM_COLUMNS = (Object.keys(props.initial_data[0].map((item, index) =>
[{
id: 'user_data',
Header: item,
headerClassName: 'family-header',
accessor: item,
className: 'centered capitalize',
show: true
}][0]
));
const columns = [
...CUSTOM_COLUMNS,
{
Header: 'Actions',
accessor: 'id',
show: true,
className: 'centered',
Cell: (props) => (
<span className="label label-danger link" onClick={this.deletePlant.bind(this, props.value)}>
<i className="fa fa-trash"></i> Delete
</span>
)
}
];
I able to correctly print the header dynamically, but my values are not coming dynamically and it's displaying my last hash key values in each column.
My header should be :
["Did I see this plant in 2016?", "Did I see this plant in 2017?", "How Many?", "User Data 4", "User Data 5", "Did I see this plant in 2022?", "Name"]
and row values should be:
Row1 : ["No", "Yes", 1, "x", "", "No", "Abronia alpina"]
Row1 : ["No", "No", 11, "x", "", "Yes", "Abronia alpina1"]
Can you please help me to get it dynamically, or let me know what I am doing wrong here. I am new in react so maybe i am missing here so please correct me.

If you are using react-table, to render the table, I think your logic of creating columns is wrong. It should be something like this.
const columns = Object.keys(response.initial_data[0]).map((key, id)=>{
return {
Header: key,
accessor: key
}
})
I have tried to create a snippet with your data. Let me know if this helps.
const ReactTable = window.ReactTable.default
const response = {
initial_data: [
{
"Did I see this plant in 2016?":"No",
"Did I see this plant in 2017?":"Yes",
"How Many?":1,
"User Data 4":"x",
"User Data 5":"",
"Did I see this plant in 2022?":"No",
"Name":"Abronia alpina"
},
{
"Did I see this plant in 2016?":"No",
"Did I see this plant in 2017?":"No",
"How Many?":11,
"User Data 4":"x",
"User Data 5":"",
"Did I see this plant in 2022?":"Yes",
"Name":"Abronia alpina1"
}]
}
class App extends React.Component {
render() {
const data = response.initial_data
const columns = Object.keys(response.initial_data[0]).map((key, id)=>{
return {
Header: key,
accessor: key
}
})
return <ReactTable
data = { data }
columns = { columns }
/>
}
}
ReactDOM.render( < App / > , 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.6/react-table.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/react-table/6.7.6/react-table.css"></link>
<div id="app"></div>

With React-Table you header must have a accessor and your data must be mapped with accessor as the key
You would create your columns like
getColumns() {
return Object.keys(data.initial_data[0]).map(key => {
return {
Header: key,
accessor: key
};
});
}
and you Table can then look like
class App extends React.Component {
getColumns() {
return Object.keys(data.initial_data[0]).map(key => {
return {
Header: key,
accessor: key
};
});
}
render() {
const columns = this.getColumns();
console.log(data.initial_data);
return (
<div>
<ReactTable
data={data.initial_data}
columns={columns}
defaultPageSize={10}
className="-striped -highlight"
/>
<br />
<Tips />
<Logo />
</div>
);
}
}
According to documentation:
accessor: 'propertyName', // or Accessor eg. (row) => row.propertyName
Accessors are functions that return the value to populate the row's
value for the column. This lets the render function not have to worry
about accessing the correct data, the value is automatically populated
in it's props.
If a string or array is passed the default accessor is used. The
default accessor will parse the input into an array and recursively
flatten it. Any values that contain a dot (.) will be split. Any
values that contain bracket ([]) will be split. This array is then
used as the path to the value to return.
Working Codesandbox

Related

Mui DataGrid filter panel value with comma separated filtering of words on my column

Is there a way to make mui DataGrid use a comma separated filter value ?
I'm using the current filter panel and have tried setting up a custom getApplyFilterFn on one of my columns. The filter shows with the custom operator but the grid doesn't update.
Shouldn't GridFilterInputValue update the grid internally ?
columns = [
{
field: "col2",
headerName: "Relation",
sortable: false,
filterOperators: [
{
label: "custom",
value: "custom",
getApplyFilterFn: (filterItem, column) => {
const value = filterItem.value || "";
return (params) => {
// const isFilter = value.split(" ").filter((word) => word !== "");
// I added true her to get it to show the filter
return true;
};
},
InputComponent: (props) => <GridFilterInputValue {...props} />,
},
],
]
Not even sure if this is possible the way I'm trying to do it.

Internal Iteration on Array.map so the value is displayed once while it is not changed

I'm building a simple News display, where the date is displayed on a Typography element and below of this Typography each Novedad is a new.
I'm trying to iterate so the date is got from the first news (the news are already ordered by date DESC). so, while other news have the same date, no new title box with the date should appear.
However I don't know how to approach this programming. Notice I'm using React and I'm trying to use conditional rendering.
In short, I'm trying to display an array of objects grouping them by date with another element showing this date.
The following code displays each news with a date title above, which is not the intented result.
novedades.map((novedad)=>{
return(
<>
<Typography variant="h6" component="div">
{moment(novedad.date).format("dddd, DD/MM/YYYY")}
</Typography>
/*The following element should be iterated so while novedad.date does not change it does not exit the loop*/
<Novedad key={novedad.id} tipo={novedad.tipo} creador={novedad.creado_por} fecha={novedad.date} contenido={novedad.contenido_html}/>
</>
)})
This is one way you could do this:
const dates=array.map(novedad=>novedad.date)
const uniqueDates=[... new Set(dates)]
... rest of code ...
{uniqueDates.map(date=>{
return (
<Typography variant="h6" component="div">
{moment(date).format("dddd, DD/MM/YYYY")}
</Typography>
{novedades.map(novedad=>{
return novedad.date==date&&<Novedad key={novedad.id} tipo={novedad.tipo} creador={novedad.creado_por} fecha={novedad.date} contenido={novedad.contenido_html}/>
})})}
You can try grouping the news by date before rendering them.
A simple solution will be to use a simple key-value pair approach like the one bellow
const [groupedNewsByDate, setGroupedNewsByDate] = useState(null);
useEffect(() => {
// fake HTTP request
Promise.resolve([
{
date: "2022-07-07T14:50:10.489Z",
id: 4,
content: "News 4"
},
{
date: "2022-07-07T14:50:10.489Z",
id: 3,
content: "News 3"
},
{
date: "2022-07-06T14:50:10.489Z",
id: 2,
content: "News 2"
},
{
date: "2022-07-05T14:50:10.489Z",
id: 1,
content: "News 1"
}
]).then((entries) => {
// grouping news that have the same date
setGroupedNewsByDate(
entries.reduce((map, item) => {
const items = map[item.date] || [];
items.push(item);
map[item.date] = items;
return map;
}, {})
);
});
}, []);
function renderNews() {
const dates = Object.keys(groupedNewsByDate);
// render fallback ui if no news
if (dates.length === 0) {
return <span>No news</span>;
}
// render news grouped by date
return dates.map((date) => (
<section key={date} aria-labelledby={date}>
<h2 id={date}>{new Date(date).toLocaleDateString()}</h2>
{groupedNewsByDate[date].map((item) => (
<div key={item.id}>{item.content}</div>
))}
</section>
));
}
Live demo:
https://codesandbox.io/s/reverent-pare-5m9mme?file=/src/App.js

I want to show contacts of an account

I want to show contacts of an account, for that I have created LWC, I am calling Apex method here and I want to show all contacts of an account using data table, but data is not showing in the UI.
I am using custom label to pass account to Apex class.
please help me to achieve this
below is my code
JS Code:
const columns = [
{ label: "Name", fieldName: "Name" },
{ label: "Phone", fieldName: "Phone"},
{ label: "Email", fieldName: "Email"}
];
#track contactsList =[];
#wire(GetContacts,{AccountId:this.AccountId})
WireContactRecords({error, data}){
console.log('Entering WireContactRecords');
console.log('Entering WireContactRecords===='+this.AccountId);
if(data){
console.log('data*********+'+JSON.stringify(data))
this.contactsList = data;
this.error = undefined;
}else{
this.error = error;
this.contactsList = undefined;
}
}
Apex class
#AuraEnabled(cacheable = true)
public static Contact GetContacts(String AccountId){
String query = 'SELECT Name,Phone,Email FROM Contact WHERE AccountId =: AccountId';
return Database.query( query );
}
HTML CODE
<lightning-datatable
data={contactsList}
columns={columns}
key-field="id"
hide-checkbox-column>
</lightning-datatable>
The syntax to pass the value of a property defined in the JS class to a wired function is: #wire(functionName, { param: '$propertyName' })
Therefore, assuming that your class has an AccountId property, you have to change
#wire(GetContacts,{AccountId:this.AccountId})
to
#wire(GetContacts,{AccountId: '$AccountId'})
Moreover in the HTML you can use only property defined in your JS class, so if columns is defined only outside it, you should provide a getter:
get columns() {
return columns;
}

Filter an array by category and keyword

So I'm trying to filter an array by the category of an item and by its title.
For example, I have a few jobs, each job is an object, containing a job title and an array of categort, so:
const category = [
'Web designer',
'App developer',
'Cleaning',
'Designer',
];
const [userSelectedCategory, setUserSelectedCategory] = useState([]);
const jobs = [
{
title: 'Job 1',
category: ['Web designer', 'App developer'],
},
{
title: 'Job 2',
category: ['Cleaning', 'Web Designer'],
},
{
title: 'Design',
category: ['Designer', 'Web Developer'],
},
];
const categoryoptions = category.map(el=>{
return <button onClick={()=>setUserSelectedCategory(prev=>{...prev, el})}>{el}</button>
//Now each time a user clicks on a button, we can compare the userSelectedCategory array to the individual job category array.
});
So my approach was to do something like this:
//This value is set whenever a user searches in search bar
const [keyword, setKeyword] = useState('');
const pattern = new RegExp('\\b' + keyword.replace(/[^a-zA-Z0-9 ]/g, ''), 'i');
const filteredjobs = jobs?.filter(
(job) =>
(keyword === '' && category.length === 0) ||
(category.some((el) => job.category.includes(el)) &&
pattern.test(job.title))
);
So the HTML would look like this:
<div>
<input onChange={e=>setKeyword(e.target.value)} />
{categoryoptions}
{filteredjobs}
</div>
But the issue with this is that if no categories are selected, then nothing will get returned as default.
So to explain how I want my filter to work,
I want all of the jobs to be displayed if the keyword is blank (input is empty). But if the input is not empty, I want to filter by that value.
In addition, I want it to filter by category even if the keyword is blank.
Lastly, I want it to filter by keyword and by category and both of them are not empty.
use conditional rendering in place of {filteredjobs}:
<input value={keyword} onChange={e => setKeyword(e.target.value)} />
{keyword ? filteredjobs : jobs}

How can i always call a function on Master - Detail Kendo grid row expansion?

I am using Master detail in Kendo Grid in AngularJS.
Here is the setup of the code:
In cshtml is:
<script type="text/x-kendo-template" id="template">
<div>
<div class="orders"></div>
</div>
</script>
In the AngularJS controller is
The MasterRowId is the id of the Column in the Master row. So the Master grid is a grid with
columns Id and name
$scope.getorders = function (masterRowId)
{
myservice.getorders(masterRowId)
.then(function (result) {
for (var j = 0; j < result.data.length; j++)
$scope.ordergroupdata.push(result.data[j]);
});
}
In the master grid definition i have
......
detailTemplate: kendo.template($("#template").html()),
detailInit: $scope.detailInit,
The definition of $scope.detailInit is
$scope.detailInit = function (e)
{
$scope.MasterRowId = e.data.Id;
$scope.getorders ($scope.MasterRowId);
var orderdata = $scope.ordergroupdata;
var orderdatasource = new kendo.data.DataSource({
transport: {
read: function (e) {
e.success(orderdata);
},
update: function (e) {
e.success();
},
create: function (e) {
var item = e.orderdata;
item.Id = orderdata.length + 1;
e.success(item);
}
},
schema: {
model: {
id: "Id",
fields: {
OrderId: { type: 'string'},
}
}
},
});
e.detailRow.find(".orders").kendoGrid({
dataSource: orderdatasource,
columns: [
{ field: "OrderId", title: "OrderId" },
]
});
}
The issue is that if i click on the first row , i can retrieve the data according to the MasterRowId from my MVC action method. So If i click on the first row and the MasterRowId is for example 10 , then i get an OrderId of "1234" .
I can click on the second row with MasterRowID of 15 and it will retrieve the OrderId of "8231", BUT if i go back to the first row the data (OrderId) in the details grid is actually the data from the second row, so its "8321" NOT "1234".
How can i always call the $scope.detailInit so that i can go back to the MVC Action method and always retrieve the correct data for that particular row with that MasterRowId ?
Once i expand the row and move on to another row , the detailsInit doesnt get called any more for that row ?
detailInit is only called on first expand, but you can use detailExpand that is called every time you expand the detail table.
Offical example:
<script>
$("#grid").kendoGrid({
columns: [
{ field: "name" },
{ field: "age" }
],
dataSource: [
{ name: "Jane Doe", age: 30 },
{ name: "John Doe", age: 33 }
],
detailTemplate: "<div>Name: #: name #</div><div>Age: #: age #</div>",
detailExpand: function(e) {
console.log(e.masterRow, e.detailRow);
}
});
</script>
Docs: detailExpand
Official example: detailExpand example

Resources