I have this code and I want to catch the errors that happens in the query used by usePaginationFragment.
what happens now is when something goes wrong in the backend, the data.queryName returns null, and there is no way to know about the errors.
the question is how can I receive the errors when happend?
function MyPagination(){
return (
<ErrorBoundary>
<Suspense fallback={"loading..."} >
<PaginationLoadable
preloadedQuery={preloadedQuery}
query={graphql`...`}
/>
</Suspense>
</ErrorBoundary>
)
}
function PaginationLoadable(this: never, props: PaginationLoadableProps){
const preloadedData = usePreloadedQuery<any>(props.query, props.preloadedQuery);
const {data} = usePaginationFragment(
props.fragment,
preloadedData
);
console.log(data[ /*queryName*/ ] === null)
}
Thank you
Here is how I did it, it must throw an error in the NetworkLayer function like so:
new Environment({
network: Network.create(
function fetch(operation, variables) {
return (
Axios
.post(
'*API_NEDPOINT*',
{
query: operation.text,
variables
}
)
.then(
response => {
if(response.data.errors){
const er: any = new Error('ServerError');
er.data = response.data.data;
er.errors = response.data.errors;
return Promise.reject(er);
}
return response.data;
}
)
);
}
),
store
});
Related
Configured my store this way with redux toolkit for sure
const rootReducer = combineReducers({
someReducer,
systemsConfigs
});
const store = return configureStore({
devTools: true,
reducer: rootReducer ,
// middleware: [middleware, logger],
middleware: (getDefaultMiddleware) => getDefaultMiddleware({ thunk: false }).concat(middleware),
});
middleware.run(sagaRoot)
And thats my channel i am connecting to it
export function createSocketChannel(
productId: ProductId,
pair: string,
createSocket = () => new WebSocket('wss://somewebsocket')
) {
return eventChannel<SocketEvent>((emitter) => {
const socket_OrderBook = createSocket();
socket_OrderBook.addEventListener('open', () => {
emitter({
type: 'connection-established',
payload: true,
});
socket_OrderBook.send(
`subscribe-asdqwe`
);
});
socket_OrderBook.addEventListener('message', (event) => {
if (event.data?.includes('bids')) {
emitter({
type: 'message',
payload: JSON.parse(event.data),
});
//
}
});
socket_OrderBook.addEventListener('close', (event: any) => {
emitter(new SocketClosedByServer());
});
return () => {
if (socket_OrderBook.readyState === WebSocket.OPEN) {
socket_OrderBook.send(
`unsubscribe-order-book-${pair}`
);
}
if (socket_OrderBook.readyState === WebSocket.OPEN || socket_OrderBook.readyState === WebSocket.CONNECTING) {
socket_OrderBook.close();
}
};
}, buffers.expanding<SocketEvent>());
}
And here's how my saga connecting handlers looks like
export function* handleConnectingSocket(ctx: SagaContext) {
try {
const productId = yield select((state: State) => state.productId);
const requested_pair = yield select((state: State) => state.requested_pair);
if (ctx.socketChannel === null) {
ctx.socketChannel = yield call(createSocketChannel, productId, requested_pair);
}
//
const message: SocketEvent = yield take(ctx.socketChannel!);
if (message.type !== 'connection-established') {
throw new SocketUnexpectedResponseError();
}
yield put(connectedSocket());
} catch (error: any) {
reportError(error);
yield put(
disconnectedSocket({
reason: SocketStateReasons.BAD_CONNECTION,
})
);
}
}
export function* handleConnectedSocket(ctx: SagaContext) {
try {
while (true) {
if (ctx.socketChannel === null) {
break;
}
const events = yield flush(ctx.socketChannel);
const startedExecutingAt = performance.now();
if (Array.isArray(events)) {
const deltas = events.reduce(
(patch, event) => {
if (event.type === 'message') {
patch.bids.push(...event.payload.data?.bids);
patch.asks.push(...event.payload.data?.asks);
//
}
//
return patch;
},
{ bids: [], asks: [] } as SocketMessage
);
if (deltas.bids.length || deltas.asks.length) {
yield putResolve(receivedDeltas(deltas));
}
}
yield call(delayNextDispatch, startedExecutingAt);
}
} catch (error: any) {
reportError(error);
yield put(
disconnectedSocket({
reason: SocketStateReasons.UNKNOWN,
})
);
}
}
After Debugging I got the following:
The Thing is that when I Provide one Reducer to my store the channel works well and data is fetched where as when providing combinedReducers I am getting
an established connection from my handleConnectingSocket generator function
and an empty event array [] from
const events = yield flush(ctx.socketChannel) written in handleConnectedSocket
Tried to clarify as much as possible
ok so I start refactoring my typescript by changing the types, then saw all the places that break, there was a problem in my sagas.tsx.
Ping me if someone faced such an issue in the future
I tried using CKEditor5 for my project and when I activated insert image and tried using it, It says ReferenceError: server is not defined. Here is the code:
class MyUploadAdapter {
constructor( loader ) {
this.loader = loader;
}
upload() {
server.onUploadProgress( data => {
loader.uploadTotal = data.total;
loader.uploaded = data.uploaded;
} );
return loader.file
.then( file => server.upload( file ) );
}
abort() {
// Reject the promise returned from the upload() method.
server.abortUpload();
}
_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();
xhr.open( 'POST', '{{ route('ck5_store')}}',true );
xhr.setRequestHeader('X-CSRF-TOKEN',$('meta[name="csrf-token"]').attr('content'));
xhr.responseType = 'json';
}
_initListeners( resolve, reject, file ) {
const xhr = this.xhr;
const loader = this.loader;
const genericErrorText = `Couldn't upload file: ${ file.name }.`;
xhr.addEventListener( 'error', () => reject( genericErrorText ) );
xhr.addEventListener( 'abort', () => reject() );
xhr.addEventListener( 'load', () => {
const response = xhr.response;
if ( !response || response.error ) {
return reject( response && response.error ? response.error.message : genericErrorText );
}
resolve( {
default: response.url
} );
} );
if ( xhr.upload ) {
xhr.upload.addEventListener( 'progress', evt => {
if ( evt.lengthComputable ) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
} );
}
}
_sendRequest( file ) {
const data = new FormData();
data.append( 'upload', file );
this.xhr.send( data );
}
}
function SimpleUploadAdapterPlugin( editor ) {
editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => {
return new MyUploadAdapter( loader );
};
}
ClassicEditor
.create( document.querySelector( '#tab-content-{{$MODULE}} form#{{$MODULE}}_form textarea[id=form_{{$MODULE}}_details]') ,
{
extraPlugins: [ SimpleUploadAdapterPlugin ],
})
.then( editor => {
console.log( editor );
} )
.catch( error => {
console.error( error );
} );
Any idea on what is the problem? Already tried looking for solutions but cannot find anywhere else. Thank you in advance.
I was having the same issue. My solution:
// Starts the upload process.
upload() {
return this.loader.file
.then( file => new Promise( ( resolve, reject ) => {
this._initRequest();
this._initListeners( resolve, reject, file );
this._sendRequest( file );
} ) );
}
// Aborts the upload process.
abort() {
if ( this.xhr ) {
this.xhr.abort();
}
}
I found this solution following documentation.
In the render props way of working with apollo graphql.. I used to be able to do this:
export const CsmartQuery = (({ children, isShowGenericErrorSnackBarForError, ...rest }) => (
<Query errorPolicy="all"
{...rest} >
{result =>
<GraphQLErrorHandler isShowGenericErrorSnackBarForError={isShowGenericErrorSnackBarForError}
result={result}>
{(transformedResult) => children(transformedResult)}
</GraphQLErrorHandler>
}
</Query>
))
My GraphQLErrorHandler, used to check the error, if its a server error (my generic catch all error), then I show an error snackbar to the user. I also process the error, strip out all the unwanted stuff and send only a code and a message to the children to work with. Here's the complete GraphQLErrorHandler component
import React from 'react';
import { showErrorSnackBarAction } from '../redux/appActions'
import { connect } from 'react-redux'
const transformError = (e) => {
if(e.networkError ||
((e.graphQLErrors || []).length === 0) ||
!e.graphQLErrors[0].extensions ||
e.graphQLErrors[0].extensions.code === 'SERVER_ERROR') {
return {
message: 'An error occurred while processing your request. Please try again',
code: 'SERVER_ERROR'
}
}
return {
message: e.graphQLErrors[0].message,
code: e.graphQLErrors[0].extensions.code,
}
};
const transformErrorsInResult = (result) => {
return {...result, error: result.error ? transformError(result.error) : undefined}
}
const GraphQLErrorHandler = ({result, isShowGenericErrorSnackBarForError, showErrorSnackBarAction, children}) => {
const [transformedResult, setTransformedResult] = React.useState(transformErrorsInResult(result))
React.useEffect(() => {
const newResult = transformErrorsInResult(result)
if(newResult.error && isShowGenericErrorSnackBarForError) {
showErrorSnackBarAction({message: newResult.error.message})
}
setTransformedResult(() => newResult)
}, [result, isShowGenericErrorSnackBarForError, showErrorSnackBarAction])
return (
children(transformedResult)
)
}
export default connect(null, { showErrorSnackBarAction })(GraphQLErrorHandler);
And I don't use Query from apollo and use the CsmartQuery component everywhere.
<CsmartQuery query={companyAddressQuery} variables={{ companyId: company.id }} isShowGenericErrorSnackBarForError>
{({ data, loading, error }) => {
if (error) return null
if (loading)
return (
<Container textAlign="center">
<Loader active />
</Container>
)
return <AddressDetailsWrapper companyAddresses={data.companyAddresses || []} company={company} {...rest} />
}}
</CsmartQuery>
How do I use the useQuery hook and be able to handle errors in a generic way? I would like the error to be transformed and a snackbar shown if needed. Preferably, in a way that I don't have to repeat code in every component.
This is what I landed up doing. Created a custom hook that wraps all apollo hooks
export const useCsmartQuery = (query, queryVariables, isShowGenericErrorSnackBarForError = true) => {
const queryResult = useQuery(query, queryVariables)
const { error } = queryResult
const dispatch = useDispatch()
if (error) {
const transformedError = transformError(error)
if (isShowGenericErrorSnackBarForError) {
dispatch(showErrorSnackBarAction({ message: transformedError.message }))
}
}
return queryResult
}
export const useCsmartMutation = (mutation, mutationData, isShowGenericErrorSnackBarForError = true) => {
const [callMutation, mutationResult] = useMutation(mutation, mutationData)
const { error } = mutationResult
const dispatch = useDispatch()
if (error) {
const transformedError = transformError(error)
if (isShowGenericErrorSnackBarForError) {
dispatch(showErrorSnackBarAction({ message: transformedError.message }))
}
return [callMutation, {...mutationResult, error: transformedError }]
}
return [callMutation, mutationResult]
}
Now use the custom hooks everywhere.
Here is my code:
#Injectable()
export class TraitementDetailEffects {
ingoing_loadDetail: { traitementID: number, obs: Promise<any> };
#Effect()
loadTraitementDetail$: Observable<Action> = this.actions$.pipe(
ofType(ETraitementDetailActions.loadTraitementDetail),
map((action: LoadTraitementDetail) => action.payload),
switchMap((traitementID) => {
if (this.ingoing_loadDetail && this.ingoing_loadDetail.traitementID === traitementID) {
return this.ingoing_loadDetail.obs;
}
const obs = this.traitementsService.loadDetail(traitementID);
this.ingoing_loadDetail = {traitementID: traitementID, obs: obs};
return obs;
}),
map(result => {
this.ingoing_loadDetail = null;
//here I don't have access to traitementID :'(
return new LoadTraitementDetailSuccess(traitementID, result);
})
);
constructor(
private actions$: Actions,
private traitementsService: TraitementsService
) {
}
}
I'm trying to pass the variable or value traitementID to the last map.
I tried to avoid the last map with an async await but then I get a weird errors "Effect dispatched an invalid action" and "Actions must have a type property" (FYI all my actions have a type property).
Try to bake this id into observable's resolve, like:
switchMap((traitementID) => {
return this.traitementsService.loadDetail(traitementID).pipe(
map(detail => ({detail,traitementID}))
);
}),
map(({detail,traitementID}) => {
...
})
I'm trying to use rxjs in conjunction with babeljs to create an async generator function that yields when next is called, throws when error is called, and finishes when complete is called. The problem I have with this is that I can't yield from a callback.
I can await a Promise to handle the return/throw requirement.
async function *getData( observable ) {
await new Promise( ( resolve, reject ) => {
observable.subscribe( {
next( data ) {
yield data; // can't yield here
},
error( err ) {
reject( err );
},
complete() {
resolve();
}
} );
} );
}
( async function example() {
for await( const data of getData( foo ) ) {
console.log( 'data received' );
}
console.log( 'done' );
}() );
Is this possible?
I asked the rubber duck, then I wrote the following code which does what I wanted:
function defer() {
const properties = {},
promise = new Promise( ( resolve, reject ) => {
Object.assign( properties, { resolve, reject } );
} );
return Object.assign( promise, properties );
}
async function *getData( observable ) {
let nextData = defer();
const sub = observable.subscribe( {
next( data ) {
const n = nextData;
nextData = defer();
n.resolve( data );
},
error( err ) {
nextData.reject( err );
},
complete() {
const n = nextData;
nextData = null;
n.resolve();
}
} );
try {
for(;;) {
const value = await nextData;
if( !nextData ) break;
yield value;
}
} finally {
sub.unsubscribe();
}
}
I think a problem with this solution is that the observable could generate several values in one batch (without deferring). This is my proposal:
const defer = () => new Promise (resolve =>
setTimeout (resolve, 0));
async function* getData (observable)
{
let values = [];
let error = null;
let done = false;
observable.subscribe (
data => values.push (data),
err => error = err,
() => done = true);
for (;;)
{
if (values.length)
{
for (const value of values)
yield value;
values = [];
}
if (error)
throw error;
if (done)
return;
await defer ();
}
}