SWR with material UI Data Grid - material-ui

I am using SWR to get the data to populate the rows of the Data Grid
fetcherGet = (url: any) => axios.get(url).then((res: {data: any}) => res.data)
let {data: customers = []} = useSWR(
[ROUTES.GET_CUSTOMERS_BY_ROUTE],
fetcherGet
)
return (
<DataGrid rows={customers} columns={columns} />
)
The issue is that when I switch to another tab and come back to the app, SWR return an undefined so an empty array is assigned and the grid gets empty. Then the data comes and the grid gets the data again.
If I keep remove [], Data Grid has an error cause undefined does not have a length attribute.
What is the best way to handle the undefined return while the data is fetched to prevent the grid to be emptied before getting the data?

I've faced the same problem with SWR + Next.js + MUI
Here is a quick and dirty solution in Typesctipt
function useUsers() {
const { data, error } = useSWR('/api/users/', fetcher)
return {
users: data !== undefined ? data : false,
isLoading: !error && !data,
isError: error
}
}
You could basically filter the return value of your custom getter hook, overriding undefined value with false.
This code fragment is based on SWR's getting started guide https://swr.vercel.app/docs/getting-started#make-it-reusable

Related

How to handle lists initial building in Getx

I'm making a reactive model with Getx on a product list, but when I start the list it comes with no value and causes an index error even though there are actually values ​​in the list it appears empty at first, which somehow gets fixed automatically. (this is inside a build of a statelesswidget)
return GetX<CartController>(
init: CartController(),
builder: (controller) {
try {
return Text(
"${StringConvert.toMoney(controller.totalByProduct[productId])}",
style: kSmallTextBold,
);
} catch (e) {
return const Text("Error...");
}
},
);
}
I did try catch to manage this, but the catch part doesn't show up;
this is relevant part of the controller
var totalByProduct = [].obs;
fetchTotal() {
List products = storage.read(StorageKeys.cartProducts);
double currentValue = 0.0;
List currentTotals = [];
for (var item in products) {
currentTotals.add(item['total'] * item['amount']);
currentValue += item['total'] * item['amount'];
}
total.value = currentValue;
totalByProduct.value = currentTotals;
}
I believe it's not the right way to do this, so what do I need to know to fix this correctly?
If helps this is the error:
With a method to read the storage (sharedPreferences) in async mode, with a FutureBuilder it was possible to correct the error, because in the initial state the list takes the value assigned explicitly. Even if it then receives the correct value, accessing the index in its initial state causes the error, this explains why even with the error it works.

ERROR TypeError: Cannot read property '_leaflet_pos' of undefined

I have angular 10 app using ngx-leaflet and routing. I have a map component, which dynamically displays custom markers on map, based on user selection. I navigate from map component view to another component. Then I navigate back to map component. User can change date, and based on that, old layer of markers is removed and new layer of markers is loaded and shown. Everything works fine, but I always get this error:
ERROR TypeError: Cannot read property '_leaflet_pos' of undefined
at getPosition (leaflet-src.js:2450)
at NewClass._getMapPanePos (leaflet-src.js:4439)
at NewClass._moved (leaflet-src.js:4443)
at NewClass.getCenter (leaflet-src.js:3798)
at NewClass.setZoom (leaflet-src.js:3181)
at SafeSubscriber._next (map.component.ts:960)
at SafeSubscriber.__tryOrUnsub (Subscriber.js:183)
at SafeSubscriber.next (Subscriber.js:122)
at Subscriber._next (Subscriber.js:72)
at Subscriber.next (Subscriber.js:49)
I can reproduce this error only when I go back to the map component. If i stay only at the map component no error is shown. I've searched for fix, but from what I found it seems nobody really knows why is this happening and how to fix this error. I've found these two issues on GitHub dealing with the same problem in Vue.js, so I guess it's problem with leaflet itself, and not ngx-leaflet.
https://github.com/vue-leaflet/Vue2Leaflet/issues/613
https://github.com/stefanocudini/leaflet-search/issues/129
I've tried to change this:
function getPosition(el) {
// this method is only used for elements previously positioned using setPosition,
// so it's safe to cache the position for performance
return el._leaflet_pos || new Point(0, 0);
}
to this:
function getPosition(el) {
// this method is only used for elements previously positioned using setPosition,
// so it's safe to cache the position for performance
if(el){
return el._leaflet_pos || new Point(0, 0);
}
else{
return new Point(0, 0);
}
}
But then the error just looks like this:
ERROR TypeError: Cannot set property '_leaflet_pos' of undefined
at setPosition (leaflet-src.js:2433)
at NewClass._resetView (leaflet-src.js:4154)
at NewClass.setView (leaflet-src.js:3174)
at NewClass.setZoom (leaflet-src.js:3186)
at SafeSubscriber._next (map.component.ts:960)
at SafeSubscriber.__tryOrUnsub (Subscriber.js:183)
at SafeSubscriber.next (Subscriber.js:122)
at Subscriber._next (Subscriber.js:72)
at Subscriber.next (Subscriber.js:49)
at MapSubscriber._next (map.js:35)
UPDATE:
As #pk. suggested in comments ,when I don't call setZoom or call it before I remove old markers I get this error:
ERROR TypeError: Cannot read property 'appendChild' of undefined
at NewClass._initIcon (leaflet-src.js:7608)
at NewClass._initIcon (leaflet.rotatedMarker.js:23)
at NewClass.onAdd (leaflet-src.js:7460)
at NewClass._layerAdd (leaflet-src.js:6572)
at NewClass.whenReady (leaflet-src.js:4433)
at NewClass.addLayer (leaflet-src.js:6634)
at NewClass.eachLayer (leaflet-src.js:6861)
at NewClass.onAdd (leaflet-src.js:6845)
at NewClass._layerAdd (leaflet-src.js:6572)
at NewClass.whenReady (leaflet-src.js:4433)
UPDATE 2:
When I don't add new marker layer to map (this.deskLayer.addTo(map)), the error dissapears, but I want to add new markers to map...
This is what happens when user changes date:
onMapReady(map: Map) {
//listening for USER DATE CHANGE
this.userFloorService.sharedUserSelectedDate
.pipe(skip(1))
.subscribe(() => {
this.deskLayer.remove(); // first remove old desks
this.userFloorService // then get desks and reservations
.getFloor(this.floorNumber)
.subscribe( (data) => {
// create new reservations
let reservationsArr = data.records[0].reservations;
// create new DESKS
let deskMarkers = [];
data.records[0].desks.forEach((desk) => {
let deskId = desk.desk_id;
let deskMarker = marker(
[
desk.desk_coordinate.coordinates[0],
desk.desk_coordinate.coordinates[1],
],
{
title: this.getDeskTitle(desk, reservationsArr), // set desk title to RESERVED/FREE
rotationAngle: desk.desk_angle,
rotationOrigin: 'center center',
riseOnHover: true
}
).on('click', () => {
this.zone.run(() => {
this.openDeskDialog(deskMarker.options.title,deskId);
});
});
deskMarker.setIcon(this.getDeskIcon(deskMarker)); // for displaying desk icons on zoomLvl -1.5
deskMarkers.push(deskMarker);
});
this.deskLayer = layerGroup(deskMarkers); // add new desks to deskLayer
this.layersControl.overlays['Desks'] = this.deskLayer; // reassign desks in overlays for correct desk layer toggling
this.deskLayer.addTo(map);
map.setZoom(-1); // set zoom
},
error =>{
console.log(error);
}
);
});
It turned out, that these errors were happening because I used BehaviorSubject to pass data between components, and everytime I navigated out and back to the map component, new subscription to BehaviorSubject was created without destroying the old subscribtion. So destroying subscriptions everytime I navigated from map component solved it. Maybe this will help to somebody.

How can I update data within Detail table and don't loose range selection and filters?

I have latest enterprise React agGrid table with Master/Detail grid. My data is fetched on the client every 5 seconds and then put immutably to the redux store. React grid component is using deltaRowDataMode={true} props and deltaRowDataMode: true in Detail options.
My master grid performs normally as expected: if I have range selected, grid would keep selection after the data updates, so would filters and visibility menu would be still opened. But Detail grid behaves differently: on data refresh selections are being removed, visibility menu closes and grid jumps if filters were changed.
I've read in docs that when I open Detail grid it's being created from scratch, but in my case I don't close Detail. Anywhere I've tried keepDetailRows=true flag, which solved problems with jumping on update and selection loss, but Detail grid doesn't update data now.
It seems there are only two possible options according to the docs https://www.ag-grid.com/javascript-grid-master-detail/#changing-data-refresh. The first one is a detail table redraws everytime a data in a master row changes and the second one is a detail row doesn't changes at all if a flag suppressRefresh is enabled. Strange decision, awful beahviour...
Update.
Hello again. I found a coupe of solutions.
The first one is to use a detailCellRendererParams in table's options and set suppressRefresh to true. It gives an opportunity to use getDetailGridInfo to get detail-table's api.
While the detail-table's refreshing is disabled, using detailGridInfo allows to set a new data to a detail-table.
useEffect(() => {
const api = gridApiRef;
api && api.forEachNode(node => {
const { detailNode, expanded } = node;
if (detailNode && expanded) {
const detailGridInfo = api.getDetailGridInfo(detailNode.id);
const rowData = detailNode.data.someData; // your nested data
if (detailGridInfo) {
detailGridInfo.api.setRowData(rowData);
}
}
});
}, [results]);
The second one is to use a custom cellRenderer, wicth is much more flexible and allows to use any content inside a cellRenderer.
In table's options set detailCellRenderer: 'yourCustomCellRendereForDetailTable.
In yourCustomCellRendereForDetailTable you can use
this.state = {
rowData: [],
}
Every cellRenderer has a refresh metod which can be used as follow.
refresh(params) {
const newData = [ ...params.data.yourSomeData];
const oldData = this.state.rowData;
if (newData.length !== oldData.length) {
this.setState({
rowData: newData,
});
}
if (newData.length === oldData.length) {
if (newData.some((elem, index) => {
return !isEqual(elem, oldData[index]);
})) {
this.setState({
rowData: newData,
});
}
}
return true;
}
Using method refresh this way gives a fully customizable approach of using a detailCellRenderer.
Note. To get a better performance with using an immutable data like a redux it needs to set immutableData to true in both main and detail tables.

RxJs Observable with infinite scroll OR how to combine Observables

I have a table which uses infinite scroll to load more results and append them, when the user reaches the bottom of the page.
At the moment I have the following code:
var currentPage = 0;
var tableContent = Rx.Observable.empty();
function getHTTPDataPageObservable(pageNumber) {
return Rx.Observable.fromPromise($http(...));
}
function init() {
reset();
}
function reset() {
currentPage = 0;
tableContent = Rx.Observable.empty();
appendNextPage();
}
function appendNextPage() {
if(currentPage == 0) {
tableContent = getHTTPDataPageObservable(++currentPage)
.map(function(page) { return page.content; });
} else {
tableContent = tableContent.combineLatest(
getHTTPDataPageObservable(++currentPage)
.map(function(page) { return page.content; }),
function(o1, o2) {
return o1.concat(o2);
}
)
}
}
There's one major problem:
Everytime appendNextPage is called, I get a completely new Observable which then triggers all prior HTTP calls again and again.
A minor problem is, that this code is ugly and it looks like it's too much for such a simple use case.
Questions:
How to solve this problem in a nice way?
Is is possible to combine those Observables in a different way, without triggering the whole stack again and again?
You didn't include it but I'll assume that you have some way of detecting when the user reaches the bottom of the page. An event that you can use to trigger new loads. For the sake of this answer I'll say that you have defined it somewhere as:
const nextPage = fromEvent(page, 'nextpage');
What you really want to be doing is trying to map this to a stream of one directional flow rather than sort of using the stream as a mutable object. Thus:
const pageStream = nextPage.pipe(
//Always trigger the first page to load
startWith(0),
//Load these pages asynchronously, but keep them in order
concatMap(
(_, pageNum) => from($http(...)).pipe(pluck('content'))
),
//One option of how to join the pages together
scan((pages, p) => ([...pages, p]), [])
)
;
If you need reset functionality I would suggest that you also consider wrapping that whole stream to trigger the reset.
resetPages.pipe(
// Used for the "first" reset when the page first loads
startWith(0),
//Anytime there is a reset, restart the internal stream.
switchMapTo(
nextPage.pipe(
startWith(0),
concatMap(
(_, pageNum) => from($http(...)).pipe(pluck('content'))
),
scan((pages, p) => ([...pages, p]), [])
)
).subscribe(x => /*Render page content*/);
As you can see, by refactoring to nest the logic into streams we can remove the global state that was floating around before
You can use Subject and separate the problem you are solving into 2 observables. One is for scrolling events , and the other is for retrieving data. For example:
let scrollingSubject = new Rx.Subject();
let dataSubject = new Rx.Subject();
//store the data that has been received back from server to check if a page has been
// received previously
let dataList = [];
scrollingSubject.subscribe(function(page) {
dataSubject.onNext({
pageNumber: page,
pageData: [page + 10] // the data from the server
});
});
dataSubject.subscribe(function(data) {
console.log('Received data for page ' + data.pageNumber);
dataList.push(data);
});
//scroll to page 1
scrollingSubject.onNext(1);
//scroll to page 2
scrollingSubject.onNext(2);
//scroll to page 3
scrollingSubject.onNext(3);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.js"></script>

How do I dynamically add a Dijit widget to a Dojo form?

I'm trying to add a new FilteringSelect widget dynamically to a preexisting form I made out of declarative tags (on page load).
prereqs = 0;
function addAnotherPrerequisite(){
var newPreReqCursor = dijit.byId("Prerequisite"+(prereqs-1)).domNode;
dojo.create("input",{
id:"prerequisite"+prereqs,
jsId:"Prerequisite"+prereqs,
dojoType:"dijit.form.FilteringSelect",
store:"PrerequisitesStore",
searchAttr:"name",
style:"width: 350px;",
required:"true",
class: "appendedPreReq"},newPreReqCursor,"after");
dojo.parser.parse( newPreReqCursor.parentNode );
prereqs++;
}
This code properly builds a FilteringSelect widget, but the widget does not seem to be registered with the form. Whenever I submit the form, none of the values in the new widgets appear. The validation attribute works, though, and it properly pulls the values from the store.I can even call the new widget via its jsId(Prerequisite1, Prerequisite2, etc) It just won't POST!
Instead of dojo.create I also tried called the FilteringSelect widget directly. This also made the widget, but did not register the values with the form during POSTing.
var filteringSelect = new dijit.form.FilteringSelect({
id: "prereq"+prereqs,
jsId: "Prerequisite"+prereqs,
store: PrerequisitesStore,
searchAttr: "name",
required: true,
style: 'width: 350px;',
class: 'appendedPreReq'
},
"prerequisite"+prereqs).startup();
I'm going crazy trying to figure this out.
So it looks like there's some sort of bug or something. I had to define the 'name' attribute explicitly to get the widget to show up in my form's .getDependents() method. That's how dijit.forms gets its list of form values. After doing this I also couldn't access this widget by dijit.byId (didn't return anything, silently caught the error I guess), so I returned the object via its jsId with an eval.
prereqs = 0;
function(){
var newPreReqCursor = eval("Prerequisite"+(prereqs-1));
newPreReqCursor = newPreReqCursor.domNode;
dojo.create("input",{
id:"Prerequisite"+prereqs,
name:"Prerequisite"+prereqs,
jsId:"Prerequisite"+prereqs,
dojoType:"dijit.form.FilteringSelect",
store:"PrerequisitesStore",
searchAttr:"name",
style:"width: 350px;",
required:"true",
class: "appendedPreReq"},newPreReqCursor,"after");
var filterSelect = dojo.parser.parse( newPreReqCursor.parentNode );
}
It is very easy. Just create a new object like that:
// first let's create an empty node (you can reuse the existing one)
var node = dojo.create("div", {
// all necessary node attributes
className: "appendedPreReq",
style: {
width: "350px"
}
}, "myAnchorNodeId", "after");
// now let's create a widget
var widget = new dijit.form.FilteringSelect(
{
// all necessary widget properties
id: "prereq" + prereqs,
store: PrerequisitesStore,
searchAttr: "name",
required: true
},
node // optional node to replace with the widget
);
Read all about it:
http://docs.dojocampus.org/dijit/info
http://docs.dojocampus.org/dijit/form/FilteringSelect
yes while creating widgets as said by Eugene Lazutkin the input type hidden related with the filtering select gets the name as of the id, and also the value of the hidden field is updating correctly. But when the filtering select is created thr .create() method we need to give the name , and also the value of the hidden field is not updating after we select some values from the filtering select(even when we blur out). Eugene Lazutkin can you let me know why its happening so... how to update the value of hidden field in the .create() method.