Populate React Table with firebase realtime database data - reactjs

I want to populate my table which i build using react library (react-table-6) with firebase data (using realtime database). I am getting values in console but not being able to put in table, each value in its own field. Values are rendering but i know im doing some silly mistake here.
See this image to see screen
Can anybody explain what im doing wrong here,
Below dropping function through which im retrieving values..
State:
this.state = {
data: [ {trainerName: '', CourseName: '', evidence: '', comment: ''}
]}
function:
get_course_list(){
return firebase.database().ref('Users/CourseApprovals/').on('value', (snapshot) => {
var data = [];
snapshot.forEach((childSnapshot) => {
var childData= childSnapshot.val();
var child1 = childData.comments;
var child2 = childData.evidence;
var child3 = childData.selectedTrainer.label;
var child4 = childData.selectedTrainer.value;
var CompleteData = {child1, child2, child3, child4};
data.push({
data: CompleteData
});
})
this.setState({
data
}, console.log(data))
})
}
componentDidMount(){
this.get_course_list();
}
And in render,
<ReactTable
data={data}
columns={[
{ Header: "SL No", maxWidth: 100,filterable: false, Cell: props => {
return <div>{props.index + 1}</div>;
}},
{ Header: "Trainer Name", accessor: "trainerName", className: "sticky", headerClassName: "sticky" },
{ Header: 'Course Name', accessor: 'CourseName'},
{ Header: "Evidence", accessor: "evidence" },
{ Header: 'Comments', accessor: 'comment'},
]}
defaultPageSize={10}
className="-striped -highlight"
/>

The problem may be in pushing data twice into the data array. Try this:
get_course_list() {
let data = [];
firebase.database().ref('Users/CourseApprovals').on('value', snapshot => {
if (snapshot.exists()) {
// making sure data exists
snapshot.forEach(child => {
let a = child.val();
// build the object
let CompleteData = {
child1: a.comments,
child2: a.evidence,
child3: a.selectedTrainer.label,
child4: a.selectedTrainer.value
}
// you are currently doing: data.push({ data: CompleteData })
// by doing so your data array looks like this:
// data:[{ data: { child1: '', ... } }, ...]
data.push(CompleteData)
// now your array should look like this:
// data:[{ child1: '', ... }, ...]
});
// setState
this.setState({ data });
console.log(data);
}
})
}
componentDidMount() {
this.get_course_list();
}

Related

using api to call users and material ui data grid to show users

i am working on an application that I make API calls to get some users for an id. the API gives you the users in an object of 25 length, and i order to get the other users u have to make other API calls.
I have a parent component from which I look for users and I pass down some variables to my child component:
<UserSection
id={id}
code={code}
open={open}
users={users}
setOpen={setOpen}
handleClose={handleClose}
handleUsers={handleUsers}
total={total}
currentPageNr={currentPageNr}
maxPageNr={maxPageNr}
/>
then in my child component I am using the material ui data grid as follows:
const [rowsState, setRowsState] = React.useState({
page: 0,
pageSize: 25,
rows: [],
loading: false,
});
const rows = [];
useEffect(() => {
let active = true;
(async () => {
setRowsState((prev) => ({ ...prev, loading: true }));
await fetchAllUsers(rowsState.page);
for (let i = 0; i < users.length; i++) {
if (users[i].campaign_id == props.id) {
let row = {
id: i + 1,
col1: i + 1,
col2: users[i].id,
col3: users[i].first_name,
col4: users[i].qualified,
col5: users[i].email,
col6: users[i].source,
col7: users[i].referrer_id,
col8: showName(users[i].referrer_id),
// col9: props.users[i].url,
col10: justSHowReached(users[i].id),
col11: users.filter(
(u) => u.referrer_id == users[i].id && u.qualified
).length,
col12: changeDate(users[i].date),
// col13: "",
};
rows[i] = row;
}
}
const newRows = rows;
console.log("new rows:", newRows);
console.log("eowsState.page:", rowsState.page);
// console.log("===**=== rowsState.pageSize:", rowsState.pageSize);
if (!active) {
return;
}
setRowsState((prev) => ({ ...prev, loading: false, rows: newRows }));
})();
return () => {
active = false;
};
}, [rowsState.page, rowsState.pageSize]);
and this is how I try to fetch users based on page number:
const fetchAllUsers = async (pageNumber) => {
console.log("----------------------------------");
console.log("page number: ", pageNumber);
console.log("----------------------------------");
await fetch(
`........./api/v1/users?page=${pageNumber}`,
{
method: "GET",
headers: new Headers({
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
}),
}
)
.then((res) => res.json())
.then(async (data) => {
// console.log("=================rows=================");
setUsers(data.data);
return data.data;
})
.catch((error) => {
console.log(error);
});
};
so I set my users here which I want to use on data model.
and also
const columns = [
{ field: "col1", headerName: "#", width: 50 },
{ field: "col2", headerName: "Id", width: 100, sortable: false },
{ field: "col3", headerName: "Name", width: 100 },
{ field: "col4", headerName: "Qualified", width: 100 },
{ field: "col5", headerName: "Email", width: 200 },
{ field: "col6", headerName: "Source", width: 75 },
{ field: "col7", headerName: "Referrer Id", width: 125 },
{ field: "col8", headerName: "Referrer Name", width: 125 },
// { field: "col9", headerName: "Url", width: 300 },
{
field: "col10",
headerName: "Reached",
width: 150,
},
{ field: "col11", headerName: "Qualified", width: 150 },
{ field: "col12", headerName: "Date Created", width: 150 },
{
field: "col13",
headerName: "Action",
width: 150,
sortable: false,
filterable: false,
hideable: false,
renderCell: (params) => {
const onClick = (e) => {
e.stopPropagation(); // don't select this row after clicking
const api = params.api;
const thisRow = {};
api
.getAllColumns()
.filter((c) => c.field !== "__check__" && !!c)
.forEach(
(c) => (thisRow[c.field] = params.getValue(params.id, c.field))
);
console.log("---->", thisRow.col2, thisRow.col4);
setUserId(thisRow.col2);
updateUser(thisRow.col2, thisRow.col4);
// return alert(JSON.stringify(thisRow, null, 4));
};
return (
<>
<Button variant="contained" onClick={onClick}>
update
</Button>
</>
);
},
},
];
this is how I make my model:
<DataGrid
// rows={rows}
columns={columns}
pagination
rowCount={props.total}
paginationMode="server"
// pageSize={25}
rowsPerPageOptions={[25]}
{...rowsState}
onPageChange={(page) => {
// console.log("and page is ", page);
setRowsState((prev) => ({ ...prev, page }));
}}
onPageSizeChange={(pageSize) =>
setRowsState((prev) => ({ ...prev, pageSize }))
}
/>
the problem is that I load users but I wont be able to show them inside my model
here u can see:
I am loading 25 users but the model doesn't show anything, however it shows me 1–25 of 5101 when i click on > I can load the users on my model like but now I am on 26–50 of 5101 so I am in page 2 but I am showing the data for page 1, when i click on > again I can see that this works but I am always behinds the correct page and sometimes Im in page 6 but I am still seeing data for page 2, and I can see that model is not being updated correctly.
on my dependency on my useEffect I have [rowsState.page, rowsState.pageSize], while the toturial says I need to have 3, and the 3rd one is the rows, if I put users there, the app will keep rendering and eventually firefox will crush. How can i make sure I am getting the correct data for every page and also how to load the data directly to the model?
There's a lot going on here, and I think your component is overall too complex. If you try and simplify things you might make it easier to see the problem.
First, I'd move the fetchAllUsers out to a separate method - it doesn't need to use state, it can just be a simple wrapper around an API call. Also, given that it's fetching a subset of users, it should probably not be called "fetchAllUsers". And, you're mixing async/await with promises - just stick with using async/await. Something like this might work
const fetchUsersForPage = async (pageNumber) => {
try {
const response = await fetch(
// Copied from your code but looks very suspicious...
`........./api/v1/users?page=${pageNumber}`,
{
method: "GET",
headers: new Headers({
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
}),
});
const { data } = await response.json();
return data;
} catch (error) {
console.log(error);
}
};
I'd also suggest you encapsulate the loading of the paged data into a separate hook. This article does a good job of explaining why you should use custom hooks for encapsulation. Your effect also has a dependency on the props.id which looks like it's a campaign id. Again, something like this might work - there's a few red flags in there which I've commented in the code below:
const usePagedData = (campaignId, page, pageSize) => {
const [loading, setLoading] = useState(false);
const [rows, setRows] = useState([]);
useEffect(() => {
const loadPageData = async () => {
setLoading(true);
const users = await fetchUsersForPage(page);
const userRows = users
// This looks suspicious - you might be losing users because they
// don't match the campaign? Shouldn't you pass the campaignId
// as part of the fetch in that case?
.filter(user => user.campaign_id === campaignId)
.map((user, index) => ({
id: index + 1,
col1: index + 1,
col2: user.id,
col3: user.first_name,
col4: user.qualified,
col5: user.email,
col6: user.source,
col7: user.referrer_id,
// Not sure what these functions are??
col8: showName(user.referrer_id),
// col9: props.users[i].url,
col10: justSHowReached(user.id),
// This number will almost certainly be wrong since 'users' is
// the list of users for this page.
col11: users.filter(u => u.referrer_id == user.id && u.qualified).length,
col12: changeDate(user.date),
// col13: "",
}));
setRows(userRows);
}
loadPageData();
}, [campaignId, page, pageSize]);
return {
rows,
loading
}
}
Now your component that contains the data grid can use your custom hook as follows:
const { rows, loading } = usePagedData(props.id, page, pageSize);

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

Get data from Server DB table on React with condition

Need help please... I have a fetchData function, getting the data from the DB Table Matricula, I just need to capture the records that have the date = Today
How can I only receive data where the date is the same as the current day?
class Matricula extends Component {
state = {
datos:[],
today: new Date()
}
componentDidMount = () => {
this.fetchData()
}
fetchData = async () => {
try {
const response = await getAll('matricula')
console.log("ver: ", response.data);
if (response.data.fecha.toLocaleString() === this.state.today.toLocaleDateString()) { // no se que me falta
this.setState({
status: "done",
datos: response.data,
});
}
} catch (error) {
this.setState({
status: "error"
});
}
};
render() {
const data = this.state.matriculas;
return (
<ReactTable
data={data}
contentEditable
filterable
collapseOnDataChange={false}
columns={[
{
Header: "Id",
accessor: "id"
},
{
Header: "Name",
accessor: "Name"
},
{
Header: "Date",
accessor: "date",
id: "date",
}
]
}
defaultPageSize={14}
className="-striped -highlight"
/>
)}
export default Matricula;
the getAll funcion is
export function getAll(entity){
return axios({
method: 'get',
baseURL: API_URL,
headers: headers(),
url: entity,
})
}
The optimal way would be to ask for the data that you need, that means asking only for the matriculas of today.
If you can't change this, what you should do is filter them before storing them in the state, something like this:
this.setState({
status: "done",
datos: response.data.filter((matricula)=>{
return matricula.date === this.state.today //not a proper dates comparison
}),
});
Here I'm assuming that your matriculas have an attribute date and I'm comparing it to your this.state.today to filter them out. Keep in mind that you should do a proper date comparison, and that depends on the format you are storing your data, this should help

How do I get this type of obj [{…}, {…}] and not this [] array?

My goal is to display events I get from firestore using ng-fullcalendar.
But the problem is, it doesn't show up in the calendar.
If used mock data it works, but on data I got from firestore doesn't
So here's the working calendar with mock data
The function
public getEvents(): Observable<any> {
const dateObj = new Date();
const yearMonth = dateObj.getUTCFullYear() + '-' + (dateObj.getUTCMonth() + 1);
let data: any = [{
title: 'namee',
start: '2018-09-19'
},
{
title: 'STI night',
start: '2018-09-18'
}
];
console.log(data);
return of(data);
}
What displays in the console.log
And how I init the calendar with the event
ngOnInit() {
this.getEvents().subscribe(eventData => {
console.log('Obs', eventData);
this.calendarOptions = {
editable: true,
eventLimit: false,
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay,listMonth'
},
events: eventData
};
});
}
The log on observable
But when I do this, the function that gets data from firestore
getEventsCollection(): Observable<any>{
this.eventService.getEventsCollection().subscribe(eventCollection => {
eventCollection.forEach(event => {
this.fullCalendarEvent = {
title: event.event_name,
start: event.event_date
}
this.eventsArray.push(this.fullCalendarEvent);
})
});
console.log( this.eventsArray );
return of(this.eventsArray);
}
Which logs this
When I replace the function
ngOnInit() {
this.getEventsCollection().subscribe(eventData => {
console.log('Obs', eventData);
this.calendarOptions = {
editable: true,
eventLimit: false,
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay,listMonth'
},
events: eventData
};
});
}
But it doesn't show up in the calendar
Maaaan, my head hurts. Any tips on how to display them would be greatly apreciated!
UPDATE
Now I see the difference,
the mock data is this [{…}, {…}] (a what might be an object)
while mine is [] an array
Anyone knows how I can do so?
It was the lifecycle hook all along!
When I put the init calendar in counstructor, voila,it worked!

How to make polling on react-table

I'm trying to build a React-Table which can make polling to a remote server every second to fetch newest data. I just followed what the author did in the doc (https://react-table.js.org/#/story/server-side-data) and tried integrate the polling function (setInterval) in "componentDidMount" but it FAILED.
The error message shows that when running "requestData" under "componentDidMount", "filtered" is undefined, whose length is not accessible. How can I fix that? Thank you.
import React from 'react';
import _ from 'lodash'
import ReactTable from "react-table";
import 'react-table/react-table.css'
const requestData = (pageSize, page, sorted, filtered) => {
return fetch(
'http://127.0.0.1:5000/agent',
{ method: 'GET'}
).then( res => res.json()
).then( filteredData => {
if (filtered.length) {
filteredData = filtered.reduce((filteredSoFar, nextFilter) => {
return filteredSoFar.filter(row => {
return (row[nextFilter.id] + "").includes(nextFilter.value);
});
}, filteredData);
}
const sortedData = _.orderBy(
filteredData,
sorted.map(sort => {
return row => {
if (row[sort.id] === null || row[sort.id] === undefined) {
return -Infinity;
}
return typeof row[sort.id] === "string"
? row[sort.id].toLowerCase()
: row[sort.id];
};
}),
sorted.map(d => (d.desc ? "desc" : "asc"))
);
const res = {
rows: sortedData.slice(pageSize * page, pageSize * page + pageSize),
pages: Math.ceil(filteredData.length / pageSize)
};
return res;
});
};
class AgentTable extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
pages: null,
// loading: true,
};
this.fetchData = this.fetchData.bind(this);
}
fetchData(state, instance) {
// this.setState({
// loading: true
// });
requestData(
state.pageSize,
state.page,
state.sorted,
state.filtered
).then(res => {
this.setState({
data: res.rows,
pages: res.pages,
// loading: false,
})
})
}
componentDidMount() {
setInterval(
() => requestData(
this.state.pageSize,
this.state.page,
this.state.sorted,
this.state.filtered
).then(res => {
this.setState({
data: res.rows,
pages: res.pages,
// loading: false,
})
}), 5000
);
}
render() {
const { data, pages, loading } = this.state;
return (
<div>
<ReactTable
columns={[
{
Header: "Agent ID",
accessor: "AGENTID"
},
{
Header: "Description",
accessor: "DESCRIPTION"
},
{
Header: "Domain",
accessor: "DOMAIN"
},
{
Header: "Register Time",
accessor: "REGTIME"
},
{
Header: "Status",
accessor: "STATUS"
},
]}
manual // Forces table not to paginate or sort automatically, so we can handle it server-side
data={data}
pages={pages} // Display the total number of pages
loading={loading} // Display the loading overlay when we need it
onFetchData={this.fetchData} // Request new data when things change
filterable
defaultPageSize={20}
className="-striped -highlight"
/>
</div>
);
}
}
export default AgentTable;
First off, you need to understand what the onFetchData callback is.
Taken from the docs, onFetchData is:
This function is called at componentDidMount and any time sorting, pagination or filterting is changed in the table
So what you're trying to achieve here won't work the way tried to.
Seeing as fetching data from a remote server every second isn't one of the conditions that invoke the onFetchData callback, you should try a different approach.
I forked React-Table's Simple Table example and added timed data requests here, this should help you get started.

Resources