error handling inside ngrx effects - ngrx-effects

I know there are examples on effect error handling all over the net but in my case I am not calling an http service which returns an observable like 99% of the examples I have found, so I am at a loss for structuring these operators to get the desired effect.
I need to move my catchError() into my switchMap()
switchMap( ( [ routerState, state ] ) => {
const tenantKey = routerState.params[ 'tenantKey' ];
if ( state ) {
if ( state.tenant && state.tenant.id === tenantKey ) {
return of( { type: '[TenantContext] Tenant Context Check Valid', payload: state.tenant } );
} else {
return of( new LoadContextAction( tenantKey ) );
}
} else {
return of( new LoadContextAction( tenantKey ) );
}
} ),
catchError( ( err ) => {
return of( { type: '[TenantContext] Error', payload: err } );
} )

I ended up replacing switchMap with map.
map( ( [ routerState, state ] ) => {
const tenantKey = routerState.params[ 'tenantKey' ];
if ( state ) {
if ( state && state.id === tenantKey ) {
return {
type: '[TenantClubContext] Tenant Context Check Valid',
payload: state
};
} else {
return new LoadContextAction( tenantKey );
}
} else {
return new LoadContextAction( tenantKey );
}
} ),
catchError( err => {
return of( { type: '[TenantClubContext] Error', payload: err } );
} )

Related

PWA cache gets deleted on redirects?

Both the static and dynamic cache get deleted from the browser when you want to visit another link. So if you go offline and try to make a request you then get an error page instead of a cached response. If you then click the back button in browser and then the forward button the cache comes back and it works. I might have something wrong with verifying the cache on activation?
This is my service worker:
const staticCacheName = "CacheV1";
const dynamicCacheName = "DynamicCacheV1";
const dynamicCacheLimit = 18;
const assets = [
'/',
'/css/main_styles.css',
'/js/ui.js',
'/js/jquery-3.6.0.slim.min.js',
'/icons/search.svg',
'/icons/favicon.svg',
'/img/bg.png',
'/img/og.png',
'/img/generated.svg',
'/icons/at.svg',
'/icons/heartFull.svg',
'/icons/comment.svg',
'/icons/share.svg',
'/icons/report.svg',
'/manifest.json',
'/fonts/titillium-web-300.woff2',
/* ... */
];
// This just deletes access dynamic cache
const limitCacheSize = (name, size) => {
caches.open(name).then(cache => {
cache.keys().then(keys => {
if(keys.length > size) {
cache.delete(keys[0]).then(limitCacheSize(name, size));
}
});
});
}
// Install service worker
self.addEventListener('install', evt => {
evt.waitUntil(
caches.open(staticCacheName).then(cache => {
cache.addAll(assets);
})
);
});
// Activate event
self.addEventListener('activate', evt => {
evt.waitUntil(
caches.keys().then(keys => {
return Promise.all( keys
.filter(key => key !== staticCacheName && key !== dynamicCacheName)
.map(key => caches.delete(key))
)
})
)
});
// Fetch event
self.addEventListener('fetch', evt => {
evt.respondWith(
caches.match(evt.request).then(cacheRes => {
return cacheRes || fetch(evt.request).then(fetchRes => {
return caches.open(dynamicCacheName).then(cache => {
if (evt.request.headers.has('range')) {
return cacheRes;
} else {
cache.put(evt.request.url, fetchRes.clone());
limitCacheSize(dynamicCacheName, dynamicCacheLimit);
return fetchRes;
}
});
});
}).catch(() => {
if (!/./.test(evt.request.url)) {
return caches.match('/img/fallback.html');
} else if(/.jpeg|.jpg|.png|.webp/.test(evt.request.url)) {
return caches.match('/img/fallbackImage.png');
} else if(/.gif/.test(evt.request.url)) {
return caches.match('/img/fallbackImage.png');
} else if(/.mp3|.ogg|.aac|.wav/.test(evt.request.url)) {
return caches.match('/img/beepBoop.mp3');
}
})
)
});

How get api result based on the error handler of Future.catchError must return a value of future's type?

I have currently this solution based on bloc. What this part of the code does is that it calls the apiCall function which is below.
class InsertApiBloc extends Bloc<InsertApiEvent, InsertApiState>
with TransitionMixin {
InsertApiBloc(this.repository) : super(FuelInsertStill()) {
on<InsertMobil>((event, emit) async {
try {
emit(InsertApiLoadingState());
var result = await apiCall(event.vehicleID, event.DateTime,
);
if (result == null) {
print("result was null error type" + errorType);
if (errorType == "AuthError") {
emit(InsertApiStateAuthError(
errorMessage: errorMessage,
));
} else if (errorType == "UnknownError") {
emit(InsertApiStateUnknownError(
errorMessage: errorMessage,
));
} else if (errorType == "Error4xx") {
emit(InsertApiStateError4xx(
errorMessage: errorMessage,
));
} else if (errorType == "GeneralError") {
emit(InsertApiStateGeneralError(
errorMessage: errorMessage,
));
}
}
if (result != null) {
emit(InsertSuccessState(
insertMessage: result.toString(),
));
}
} catch (exception) {
emit(InsertApiStateUnknownError(
errorMessage: exception.toString(),
));
}
});
}
Future<dynamic> apiCall(String vehicleID, String DateTime,
) async {
var future = repository
.insertData(
vehicleID: vehicleID,
dateTime: DateTime)
.catchError((err, stackTrace) {
if (err is AuthError) {
errorType = "AuthError";
errorMessage = err.message;
} else if (err is UnknownError) {
errorType = "UnknownError";
errorMessage = err.message;
} else if (err is Error4xx) {
errorType = "Error4xx";
errorMessage = err.message;
} else {
errorType = "GeneralError";
errorMessage = "Something went wrong reason:unknown";
}
});
cancelableCall = CancelableOperation.fromFuture(future);
return cancelableCall.valueOrCancellation(null);
}
The error I have debug and it refers to the apicall function below example
else if (err is Error4xx) {
errorType = "Error4xx";
errorMessage = err.message;
}
it expects the it to be return Future etc. So what I have done now is resolve the apiCall like this with the then part.
var future = repository
.insertData(
vehicleID: vehicleID,
dateTime: DateTime)
.then((val) {
//print("success");
}).catchError((err, stackTrace){....
But then the result is not able to be sent back to this at it stuck in the apiCall part the then and stop there.
var result = await apiCall(event.vehicleID, event.DateTime,
);
How can I resolve so that it works as it is usually sending back the result to the above var result?

Redux Toolkit - action from one slice to be caught in another slice

There's an action (addOrder) in an orderSlice
orderSlice.js
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
orders: []
};
const ordersSlice = createSlice({
name: 'orders',
initialState,
reducers: {
addOrder: {
reducer: (state, action) => {
state.orders.push(action.payload)
},
prepare: (orderItems, orderTotal) => {
const orderDate = new Date().toDateString();
return { payload: { orderDate, orderItems: orderItems, orderTotal: orderTotal }}
}
}
}
})
export const { addOrder } = ordersSlice.actions;
export default ordersSlice.reducer;
I'd like it to also affect the state in another slice (cartSlice). Once the 'addOrder' is fired, it should also bring the cartReducer to its initial state. Some googling suggested that I should use extrareducers for that but I'm really not getting its syntax. See below (not valid code in extrareducers)
cartSlice
import { createSlice } from '#reduxjs/toolkit';
import { addOrder } from './ordersSlice';
const initialState = {
items: {},
totalAmount: 0
};
const cartSlice = createSlice({
name: 'cart',
initialState: initialState,
reducers: {
addToCart: (state, action) => {
// p = product to be added or amended
const p = action.payload;
if (state.items[p.id]) {
// already exists
state.items[p.id].quantity += 1;
state.items[p.id].sum += p.price;
state.totalAmount += p.price;
} else {
state.items[p.id] = { price: p.price, quantity: 1, title: p.title, sum: p.price};
state.totalAmount += p.price;
}
},
removeFromCart: (state, action) => {
console.log('remove from cart');
console.log(action.payload);
const currentQuantity = state.items[action.payload].quantity;
console.log(currentQuantity);
if (currentQuantity > 1) {
state.items[action.payload].quantity -= 1;
state.items[action.payload].sum -= state.items[action.payload].price;
state.totalAmount -= state.items[action.payload].price;
} else {
state.totalAmount -= state.items[action.payload].price;
delete state.items[action.payload];
}
}
},
extraReducers: (builder) => {
builder
.addCase(addOrder(state) => {
return initialState;
})
}
});
export const { addToCart, removeFromCart } = cartSlice.actions;
export default cartSlice.reducer;
You're almost there! The builder.addCase function takes two arguments. The first is the action creator and the second is the case reducer. So you need a comma after addOrder.
extraReducers: (builder) => {
builder.addCase(addOrder, (state) => {
return initialState;
});
}

Does React-data-grid have a mechanism to handle pagination?

I didn't see anything in the docs for pagination. Is there a built-in mechanism for this, or would I have to implement it myself?
Here is an example of pagination (Infinite scrolling) in react data grid. I am using the scrollHeight,scrollTop and clientHeight properties to check whether to load next page.You need to modify your API's to support this type of pagination.
let columns = [
{
key: 'field1',
name: 'Field1 ',
},
{
key: 'field2',
name: 'Field2 ',
},
{
key: 'field3',
name: 'Field3',
},
]
export default class DataGrid extends React.Component {
constructor(props) {
super(props)
this.state = {height: window.innerHeight - 180 > 300 ? window.innerHeight - 180 : 300,page:1}
this.rowGetter = this.rowGetter.bind(this)
this.scrollListener = () => {
if (
(this.canvas.clientHeight +
this.canvas.scrollTop) >= this.canvas.scrollHeight) {
if (this.props.data.next !== null) {
let query = {}
let newpage = this.state.page +1
query['page'] = newpage
this.setState({'page':newpage})
this.props.dispatch(fetchData(query)).then(
(res) => {
// make handling
},
(err) => {
// make handleing
}
)
}
}
};
this.canvas = null;
}
componentDidMount() {
this.props.dispatch(fetchData({'page':this.state.page}))
this.canvas = findDOMNode(this).querySelector('.react-grid-Canvas');
this.canvas.addEventListener('scroll', this.scrollListener);
this._mounted = true
}
componentWillUnmount() {
if(this.canvas) {
this.canvas.removeEventListener('scroll', this.scrollListener);
}
}
getRows() {
return this.props.data.rows;
}
getSize() {
return this.getRows().length;
}
rowGetter(rowIndex) {
let rows = this.getRows();
let _row = rows[rowIndex]
return _row
}
render() {
return (
<ReactDataGrid columns={columns}
rowGetter={this.rowGetter}
rowsCount={this.getSize()}
headerRowHeight={40}
minHeight={this.state.height}
rowHeight={40}
/>
)
}
}
Note : Assumed data are taken from redux store

Geolocation after the device is ready

I have a iphone app built with jquery mobile which is wrapped in phonegap. I am trying to get geolocation after the device is ready to get rid of the nasty alert /var/mobile/Applications/157EB70D-4AA7-826E-690F0CBE0F/appname.app/www/index.html.
I have set some alerts to see if the device is ready and it keeps saying isDeviceReady is:false which is meaning the device is not ready
he is the code
$(function() {
var isWatching = false;
var isAndroid = ( navigator.userAgent.indexOf('Android') != -1 ) ? true : false;
var isDeviceReady = false;
document.addEventListener("deviceready", onDeviceReady, false);
function onDeviceReady() {
isDeviceReady = true;
}
function getClientPosition(id, successCallback, errorCallback) {
if ( isDeviceReady ) {
var hasFoundMarker = false;
if ( hasClientPosition() ) {
$(id).gmap('findMarker', 'tag', 'client', function(found, marker) {
if (found) {
hasFoundMarker = true;
marker.setPosition(getClientLatLng());
$(id).gmap('getMap').setCenter(marker.getPosition());
if ( $.isFunction(successCallback) ) {
successCallback.call(this, getClientLatLng());
}
}
});
if ( !hasFoundMarker ) {
addClientMarker(id, getClientLatLng());
if ( $.isFunction(successCallback) ) {
successCallback.call(this, getClientLatLng());
}
}
}
if ( !isWatching[id] ) {
if ( navigator.geolocation ) {
isWatching[id] = true;
if ( isAndroid ) {
watch[id] = setInterval(function() {
navigator.geolocation.getCurrentPosition (
function( position ) {
var latlng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
$(id).gmap('findMarker', 'tag', 'client', function(found, marker) {
if (found) {
hasFoundMarker = true;
marker.setPosition(latlng);
$(id).gmap('getMap').setCenter(latlng);
}
});
if ( !hasFoundMarker ) {
addClientMarker(id, latlng);
}
setData(CLIENT_HAS_POSITION, true);
setData(CLIENT_LATITUDE, latlng.lat());
setData(CLIENT_LONGITUDE, latlng.lng());
if ( $.isFunction(successCallback) ) {
successCallback.call(this, latlng);
}
},
function( error ) {
if ( $.isFunction(errorCallback) ) {
errorCallback.call(this, error);
}
},
opts.geolocationOptions
);
}, 5000);
} else {
watch[id] = navigator.geolocation.watchPosition (
function( position ) {
var latlng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
$(id).gmap('findMarker', 'tag', 'client', function(found, marker) {
if (found) {
hasFoundMarker = true;
marker.setPosition(latlng);
$(id).gmap('getMap').setCenter(latlng);
}
});
if ( !hasFoundMarker ) {
addClientMarker(id, latlng);
}
setData(CLIENT_HAS_POSITION, true);
setData(CLIENT_LATITUDE, latlng.lat());
setData(CLIENT_LONGITUDE, latlng.lng());
if ( $.isFunction(successCallback) ) {
successCallback.call(this, latlng);
}
},
function( error ) {
if ( $.isFunction(errorCallback) ) {
errorCallback.call(this, error);
}
},
opts.geolocationOptions
);
}
}
}
} else {
var timer = setTimeout(function() {
getClientPosition(id, successCallback, errorCallback);
alert('Trying to get client position. This message will pop up again in 15 sec, unless device is ready. IsDeviceReady is: '+isDeviceReady);
}, 15000);
}
}
Anyone have any ideas where i am going wrong?