dynamic antd table title - react - reactjs

I am using antd table here, I successfully populated one of the title vertically and I wanna populate the other one horizontally, refer to the images for better clarification thanks
here is my table column:-
const columns = [
{
title: 'Days',
dataIndex: 'date',
defaultSorter: 'ascend',
key: 'title',
sorter: (a, b) => a.date.localeCompare(b.date),
sortDirections: ['descend', 'ascend'],
render: (date) => getDayName(new Date(date)),
},
{
title: 'period',
dataIndex: 'period',
},
}
Datasource or data from api
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [query, setQuery] = useState('');
const fetchData = async () => {
setLoading(true);
const { data } = await getTimetable();
setData(data);
setLoading(false);
};
useEffect(() => {
fetchData();
}, []);
rendering table data:-
<TableContainer columns={columns} rowKey={(record) => record.login.uuid} dataSource={data} />
What I want to achieve :point_down:
what I have right now:-

In antd Table, columns need to passed as below format.
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
// .... rest of the cols
];
So to create dynamic columns from the response data you get here, 1st you need to create the array of objects (cols) as the above format that need to pass for <Table columns={formattedColsfromResponseData}.
So in your case you need to create columns[] like below format.
let cols = [
{
title: "Days",
dataIndex: "date",
defaultSorter: "ascend",
key: "title",
sorter: (a, b) => a.date.localeCompare(b.date),
sortDirections: ["descend", "ascend"]
render: (date) => getDayName(new Date(date)),
},
{
title: "period 1"
key: "period 1"
render: (row) => row.section + " - " + row.subject; // render function for customized data.
},
{
title: "period 2"
key: "period 2"
render: (row) => row.section + " - " + row.subject; // render function for customized data
},
// .... rest of the 'Period n' cols in the response.
];
With the below method you can create cols as required format by passing the response data.
This method works assuming response data has only unique 'Periods'.
const generateColumns = (data) => {
let cols = [];
const days = {
title: "Days",
dataIndex: "date",
defaultSorter: "ascend",
key: "title",
sorter: (a, b) => a.date.localeCompare(b.date),
sortDirections: ["descend", "ascend"]
render: (date) => getDayName(new Date(date)),
};
cols.push(days); // add 'Days' obj to Columns.
// for render: property in cols need to return a function. Here creates that.
const generateRender = (row) => {
console.log("gen row----", row);
return (row) => row.section + " - " + row.subject;
};
// create the col objects for each 'Period'. This method works assuming response data has only unique 'Periods'.
data.map((row) => {
let period = {}; // for create 'Period' obj for cols.
period["title"] = row.period;
period["key"] = row.period;
period["render"] = generateRender(row); // only need this if you render customized data.
cols.push(period); // add Current Period obj to Columns.
});
return cols;
};
Now you can pass this method to columns prop in Table with response data which returns the dynamic cols.
const App = () => (
<div>
<Table columns={generateColumns(data)} dataSource={data} />
</div>
);
Check this full demo antd-dynamic-cols-example code.

Related

How to parse data from firebase to MUI DataGrid?

I'm trying to learn how to parse data from firebase to MaterialTable because it looks way more clean than doing it "manually" with maps and <th> , <tr> but I haven't seen any tutorial that links MaterialTable + firebase. All tutorials so far show you can parse some manual data like this:
But not a single one with firebase included. any documentations/tips/help is welcome.
My code so far:
Data from firebase (working)
const [estudiantes, setEstudiantes] = useState([]);
const estudiantesRef = db.collection("usuarios").doc(user.uid).collection("estudiantes")
useEffect(() => {
estudiantesRef
.orderBy('name')
.onSnapshot(snapshot => {
const tempData = [];
snapshot.forEach((doc) => {
const data = doc.data();
tempData.push(data);
});
setEstudiantes(tempData);
})
}, []);
Columns (Working)
const columns = [
{ field: 'id', headerName: 'ID', width: 100 },
{field: 'nombre', headerName: 'Nombre', width: 200},
{field: 'colegio', headerName: 'Colegio', width: 250},
{field: 'grado', headerName: 'Grado', width: 150}
]
And how i'm "rendering" the table (Working)
return (
<div className = "table_container" >
<DataGrid
rows={tempData}
columns={columns}
pageSize={5}
rowsPerPageOptions={[5]}
checkboxSelection
/>
</div>
)
}
export default ListadoEstudiantes
My data half attempt (I'm constantly trying stuff)
const [data, setData] = useState();
useEffect(() => {
estudiantes.map((estudiantes, index) => ({
setData(estudiantes[index])
}))
}, [estudiantes])
that's giving me an error so far but I'll keep trying stuff until I get it. This is how I expect to make it look with the data from Firebase
Any tips/documentation/help is welcome
Well Apparently you don't have to do anything fancy I found out that as long as the variables names inside your doc(). match the ones you set up in your columns options
Set up in the code:
in the code you can see when I set up the values it matches with the names of the columns variables
const register = (e) => {
e.preventDefault();
const docRef = db.collection('usuarios').doc(user.uid).collection('estudiantes').doc();
docRef.set({
nombre: firstName + " " + lastName,
colegio: escuela,
grado: grado,
uid: docRef.id,
}).then((r) => {
history.push("/Inicio");
})
}
How it should look in the firebase (still matching the columns)
Finally the result:

In react-table with React Hooks, how to make a get request inside Cell?

I have been working with React-table for a couple of days now, my first time using it, and I have ran into some issues I can't quite seem to resolve. I am trying to build a table where I can show data from two API get requests at the same time, and since I don't know if there is a way to connect the two requests data into one object, and I wouldn't know how to do it, I was trying to access some of the data with get requests inside the react-table Column Cell itself.
My case being: I have two objects, Contacts and Institutions, contacts have in their data the institution ID as parameter, and I need to show in the table both the contact information and some information of the institution that is linked to it, getting it from the institution ID that is present in the contact data.
Here is one example of contact:
{
"contact_id": "34378a25-fe8c-4c64-bd35-59eab3f30863",
"institution_id": "ae1d0fe8-cce1-40ef-87d7-729dfbe9716d",
"name": "Contato 2",
"role": "Cargo 1",
"phone_numbers": [],
"emails": [],
"createdAt": "2021-03-09T20:40:26.6863764Z",
"updatedAt": "2021-03-09T20:40:26.686376448Z",
"deleted": false
}
And here is the institution data:
{
"institution_id": "ae1d0fe8-cce1-40ef-87d7-729dfbe9716d",
"name": "Matheus Salles Blanco",
"socialReason": "teste",
"cnpj": "99999999999999",
"abbreviation": "Matheus",
"website": "teste.com",
}
This is the code that is being implemented, reduced to only the parts that matter and that is working, but only showing the info that is being fetched from the contact object:
const Contacts = ({ match }) => {
const [data, setData] = useState([]);
const [institution, setInstitution] = useState();
const dataRecoil = useRecoilValue(contactData);
const handleContact = useCallback(async () => {
const response = dataRecoil.data;
if (response) {
setData(response.filter((contact) => !contact.deleted));
}
}, [setData, dataRecoil]);
useEffect(() => {
handleContact();
}, [handleContact]);
const columns = useMemo(
() => [
{
Header: 'Nome',
accessor: 'name',
},
{
Header: 'Sigla',
accessor: 'abbreviation',
},
{
Header: 'Nome Fantasia',
accessor: 'institution_id',
},
],
[editToggle, handleDelete],
);
return (
<>
<Table columns={columns} data={data} />
</>
);
};
And a print of it:
And here is what I have been trying to do:
const Contacts = ({ match }) => {
const [data, setData] = useState([]);
const [institution, setInstitution] = useState();
const dataRecoil = useRecoilValue(contactData);
const handleContact = useCallback(async () => {
const response = dataRecoil.data;
if (response) {
setData(response.filter((contact) => !contact.deleted));
}
}, [setData, dataRecoil]);
useEffect(() => {
handleContact();
}, [handleContact]);
const columns = useMemo(
() => [
{
Header: 'Nome',
accessor: 'name',
},
{
Header: 'Sigla',
accessor: 'abbreviation',
},
{
Header: 'Nome Fantasia',
accessor: 'institution_id',
Cell: async ({ cell }) => {
const response = await getInstitutionById(cell.row.values.institution_id);
const result = [response.data];
const inst = result.map((inst) => {return inst.name});
const institution_name = inst[0];
console.log(institution_name);
return institution_name;
},
},
],
[editToggle, handleDelete],
);
return (
<>
<Table columns={columns} data={data} />
</>
);
};
Which works at the level of fetching the right data, but does not render the page and shows errors:
The error
The right data being shown in the console.log
The expected output would be to show those names on the console.log on place of that long ID of the first picture.
So, is it possible to do what I am trying to do? And if so, what might am I be doing wrong?
I believe the issue is that you are providing an async function for your cell, which will return a Promise, not the institution name as you are expecting.
A potential solution is to instead create a custom Cell component that uses state to store the institution name. I have provided an example below, which was guided by this example, however I have not tested the code at all, so use it as more of a guide.
const MyCell = ({ cell }) => {
const [institutionName, setInstitutionName] = useState('fetching...')
useEffect(() => {
const getInstitutionName = async (id) => {
const response = await getInstitutionById(id);
const result = [response.data];
const inst = result.map((inst) => {return inst.name});
const institution_name = inst[0];
console.log(institution_name);
setInstitutionName(institution_name)
}
getInstitutionName(cell.row.values.institution_id)
}
return institutionName
}
const Contacts = ({ match }) => {
const [data, setData] = useState([]);
const [institution, setInstitution] = useState();
const dataRecoil = useRecoilValue(contactData);
const handleContact = useCallback(async () => {
const response = dataRecoil.data;
if (response) {
setData(response.filter((contact) => !contact.deleted));
}
}, [setData, dataRecoil]);
useEffect(() => {
handleContact();
}, [handleContact]);
const columns = useMemo(
() => [
{
Header: 'Nome',
accessor: 'name',
},
{
Header: 'Sigla',
accessor: 'abbreviation',
},
{
Header: 'Nome Fantasia',
accessor: 'institution_id',
Cell: MyCell
},
],
[editToggle, handleDelete],
);
return (
<>
<Table columns={columns} data={data} />
</>
);
};

Search Input for ANTD Table

I've managed to make a search input that allow search for the title and category of the project from the antd table but the initial data {dataSource} is not loaded with the data in dataLog (not sure is it because of AJAX request) and thus not loaded into the table, the data will only be shown when the first Search is performed at this case, here is my code:
const ListLogs = () => {
const [logs, setLogs] = useState([]);
const [search, setSearch] = useState("");
// const [latestFive, setLatestFive] = useState([]);
const [value, setValue] = useState("");
const timeAgo = (prevDate) => {
const diff = Number(new Date()) - prevDate;
const minute = 60 * 1000;
const hour = minute * 60;
const day = hour * 24;
const month = day * 30;
const year = day * 365;
switch (true) {
case diff < minute:
const seconds = Math.round(diff / 1000);
return `${seconds} ${seconds > 1 ? "seconds" : "second"} ago`;
case diff < hour:
return Math.round(diff / minute) + " minutes ago";
case diff < day:
return Math.round(diff / hour) + " hours ago";
case diff < month:
return Math.round(diff / day) + " days ago";
case diff < year:
return Math.round(diff / month) + " months ago";
case diff > year:
return Math.round(diff / year) + " years ago";
default:
return "";
}
};
const getAllLogs = async () => {
try {
const response = await fetch("http://localhost:5000/logs/");
const jsonData = await response.json();
setLogs(jsonData);
} catch (err) {
console.error(err.message);
}
};
const expandedRowRender = () => {
const columns = [
{ title: "Date", dataIndex: "date", key: "date" },
{ title: "Name", dataIndex: "name", key: "name" },
{
title: "Status",
key: "state",
render: () => (
<span>
<Badge status="success" />
Finished
</span>
),
},
{ title: "Upgrade Status", dataIndex: "upgradeNum", key: "upgradeNum" },
{
title: "Type",
dataIndex: "operation",
key: "operation",
render: () => {
return (
<div>
<Tag color="green">CREATE</Tag>
<Tag color="gold">UPDATE</Tag>
<Tag color="red">DELETE</Tag>
</div>
);
},
},
];
const data = [];
for (let i = 0; i < 3; ++i) {
data.push({
key: i,
date: "2014-12-24 23:12:00",
name: "This is production name",
upgradeNum: "Upgraded: 56",
});
}
return <Table columns={columns} dataSource={data} />;
};
const dataLog = [];
for (let i = 0; i < logs.length; i++) {
dataLog.push({
key: i,
category: <Tag color="default">{logs[i].category}</Tag>,
title: logs[i].title,
id: logs[i].id,
lastUpdated:
new Date(logs[i].last_updated).toLocaleString() +
" " +
timeAgo(new Date(logs[i].last_updated).getTime()),
});
}
const [dataSource, setDataSource] = useState(dataLog);
console.log("dataSource: ", dataSource);
console.log("dataLog: ", dataLog);
const columns = [
{
title: "Category",
dataIndex: "category",
key: "category",
},
{
title: "Title",
dataIndex: "title",
key: "title",
},
{ title: "ID", dataIndex: "id", key: "id" },
{ title: "Last Updated", dataIndex: "lastUpdated", key: "lastUpdated" },
];
useEffect(() => {
setDataSource(dataLog);
getAllLogs();
}, []);
return (
<Fragment>
<div>
<header className="headerPage">
<h1> Logs </h1>
</header>
</div>
<div className="container">
<Input.Search
placeholder="Input search text"
value={value}
onChange={(e) => {
const currValue = e.target.value;
setValue(currValue);
const filteredData = dataLog.filter(
(entry) =>
entry.title.toLowerCase().includes(currValue.toLowerCase()) ||
entry.category.props.children
.toLowerCase()
.includes(currValue.toLowerCase())
);
console.log("filtered Data: ", filteredData);
setDataSource(filteredData);
}}
// allowClear
// onChange={(e) => setSearch(e.target.value)}
// style={{ width: 200, float: "right" }}
/>
<Table
bordered
className="components-table-demo-nested"
onRow={(i) => ({
onClick: (e) => {
history.push(
`/admin/viewLog/${i.id}/${i.category.props.children}`
);
},
})}
columns={columns}
// expandable={{ expandedRowRender }}
dataSource={dataSource} //tried {dataSource ? dataSource: dataLog} does not work as well
size="small"
pagination={false}
/>
</div>
</Fragment>
);
};
export default ListLogs;
Please enlighten me for this! Thank you
Short answer:
When we call setDataSource(dataLog) inside the useEffect hooks for initial render (mounting) that would save empty array in dataSource variable, which can be used for showing empty list icon or loading, we have to set data source again when the data is fetched so, we can use another useEffect hook for logs state variable and set data source in it.
useEffect(() => {
setDataSource(dataLog);
}, [logs]);
Details:
What we are trying to achieve with these hooks i.e. useEffect, is that we can write a code which can react when defined action occur, e.g. what we did in useEffect is that on initial render (mount phase), we set empty array and then call the API. After that when data arrives we set logs i.e. setLogs(jsonData).
Which will populate the logs variable, then that loop come into play and dataLog will get filled but after that we never set the data source again with this DataLog i.e. filled list of objects (we only did that in for mount phase in which dataLog was empty, or when onChange get triggered)
So, a simple solution can be to use useEffect hooks for logs variable so, whenever the logs variable change, it will set the data source as well. As defined above in short answer.
Thus, with these hooks, we can significantly refactor this code.
One More Thing:
I recommend using getAllLogs function with await keyword, that will make that async code works like sync one i.e.
useEffect(async () => {
await getAllLogs();
}, []);
I (try to) reproduce it here

React Material-Table editing from props using hooks

I am building an application that will request data from an API and display it in an editable table, where the user can edit and update the data base. I am using React with material-ui and material-table.
I will initialize the data in the state of the parent component, and pass it as props to the child component that renders the table. For test purposes, I initialize the data in the state to simulate later implementation of props. The table renders correctly, but when I edit, the values don't change.
export default function Table(props){
const [gridData, setGridData] = useState({
data: [
{ param: "Admin", val: "0.03" },
{ param: "Margin", val: "0.4" },
{ param: "Price", val: "5080" },
],
resolve: () => {}
});
useEffect(() => {
gridData.resolve();
}, [gridData]);
const onRowUpdate = (newData, oldData) =>
new Promise((resolve, reject) => {
const { data } = gridData;
const index = data.indexOf(oldData);
data[index] = newData;
setGridData({ ...gridData, data, resolve });
});
const { data } = gridData;
return (
<div>
<MaterialTable
columns={props.col}
data={data}
editable={{
isEditable: rowData => true,
isDeletable: rowData => true,
onRowUpdate: onRowUpdate
}}
/>
</div>
);
}
Now, I found that the table works properly when I replace the columns={props.col} line with this:
columns={[
{ title: 'Parameters', field: 'param', editable: 'never' },
{ title: 'Value', field: 'val', editable: 'onUpdate' }
]}
So it appears that my problem is with the columns and not the data.
Any help would be greatly appreciated!
NOTE:
the code is based on this response from github: https://github.com/mbrn/material-table/issues/1325
EDIT:
The columns are passed from the parent component like this:
const comonscol = [
{ title: 'Parameters', field: 'param', editable: 'never' },
{ title: 'Value', field: 'val', editable: 'onUpdate' }
];
export default function ParamsSection(props) {
...
return (
<div>
...
<Table col={comonscol} data={dummy2} />
...
</div>
);
}
I'm not quite sure about what causing this issue but it seems that MaterialTable component doesn't trigger a re-render when columns data passed as a porps.
Here is how I fixed it:
First Approach:
Create a new state for columns and trigger re-render by updating the columns via useEffect:
const [gridData, setGridData] = useState(props.data);
const [columns, setcolumns] = useState(props.col);
useEffect(() => {
gridData.resolve();
// update columns from props
setcolumns(props.col);
}, [gridData, props.col]);
...
const onRowUpdate = (newData, oldData) =>
new Promise((resolve, reject) => {
// Reset the columns will trigger re-render as the state has changed
// then it will update by useEffect
setcolumns([]);
const { data } = gridData;
const updatedAt = new Date();
const index = data.indexOf(oldData);
data[index] = newData;
setGridData({ ...gridData, data, resolve, updatedAt });
});
codeSandbox Example.
Second Approach:
Merge data, columns into a state of object and make a copy of props data then use that copy. (I've changed the date structure a bit for testing)
// Parent
const data = [
{ param: "Admin", val: "0.03" },
{ param: "Margin", val: "0.4" },
{ param: "Price", val: "5080" }
];
const comonscol = [
{ title: "Parameters", field: "param" },
{ title: "Value", field: "val" }
];
...
<Table col={comonscol} data={data} />
// Table.js
const [gridData, setGridData] = useState({
data: props.data,
columns: props.col,
resolve: () => {},
updatedAt: new Date()
});
const onRowUpdate = (newData, oldData) =>
new Promise((resolve, reject) => {
// Copy current state data to a new array
const data = [...gridData.data];
// Get edited row index
const index = data.indexOf(oldData);
// replace old row
data[index] = newData;
// update state with the new array
const updatedAt = new Date();
setGridData({ ...gridData, data, updatedAt, resolve });
});
codeSandbox Example.
Note: onRowUpdate here as an example, same goes for onRowAdd, onRowDelete

React.useMemo does not update the data

I am new to hooks. So this might be easy yet I have no idea how to solve:
I have a function like this which takes two arrays columns and data . and those data should be memoized or else it does not work. (recommended by react-table guys)
function ReactTable(props) {
const columns = React.useMemo(() => props.columns, [])
const data = React.useMemo(() => props.data, [])
return <Table columns={columns} data={data} />
}
this works fine but when the props change (say an item is added or removed from data array), the React.useMemo won't send the updated data to the Table component. How can I resolve this :(
This is exactly what the dependency array in hooks is for. You can define variables that 'trigger' the change on hooks. In your case this would mean that you would need to change your code to the following:
function ReactTable(props) {
const columns = React.useMemo(() => props.columns, [props.columns]);
const data = React.useMemo(() => props.data, [props.data]);
return <Table columns={columns} data={data} />
}
This means, whenever props.columns changes, the columns variable is updated and the same for props.data and data.
Mind you above answer from user Linschlager might not work if you're using react-table's sorting hook useSortBy. Indeed, the authors final solution did not involve react.useMemo.
To me it worked out anyways. My columns- and row-data came from a query-data object that I had to resolve to fit the specific way react-table does it.
It looked something like this:
function ReportTable({ queryData }) {
... other data such as {selectedPerLevel} ...
/* COLUMNS */
let firstColumn = {
Header: ' ',
columns: [
{
Header: selectedPerLevel.name,
accessor: 'perLevel',
},
],
};
let otherColumns = [];
queryData.weeks.forEach((week) => {
let otherColumn = {
Header: week,
Footer: ' ',
center: true,
columns: [
{
Header: 'Ratio',
accessor: `ratio${week}`,
},
{
Header: 'Count',
accessor: 'count' + week,
},
],
};
otherColumns = [...otherColumns, otherColumn];
});
/* ROWS */
let listOfRows = queryData.units.map((unit) => {
let row = {};
// for each column
unit.items.forEach(({ week, ratio, count}) => {
row = {
...row,
['ratio' + week]: ratio,
['count' + week]: count,
};
});
// add the first column-data to the row
row = { ...row, perLevel: unit.name, id: unit.id };
return row;
});
const data = React.useMemo(() => listOfRows, [queryData]);
const columns = React.useMemo(() => [{ ...firstColumn }, ...otherColumns], [queryData]);
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({data, columns}, useSortBy);
return <Table ....
I don't know if this is to anyones help but it all worked out fine for me with above solution.

Resources