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
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');
}
})
)
});
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?
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;
});
}
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
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?