Apollo error handling in one point in application - reactjs

I would like to add error handling in my apollo react app. I'm checking error
export enum ERROR_CODES {
ERROR_USER_NOT_EXIST = "ERROR_USER_NOT_EXIST"
}
export const getErrorMessage = (error: string): string | null => {
switch(error) {
case ERROR_CODES.ERROR_USER_NOT_EXIST:
return 'test error';
default:
return null;
}
}
and I want to show snackBar for errors which I have in switch case.
I understand that i can't do it with apollo-link-error because i want to show an error like react component and I don't want to add error handling for each query request in my components. Maybe exist way to do it in one point in my app and without apollo-link-error.

Use apollo-link-error ;)
Nobody forces you to only console.log() error or break a data flow. You can use it to "inject your hook" only, to be notified on errors.
You can wrap your app in some context provider - create a 'communication channel' - use it to write errors (from error link) and to render (read and clear) them in snackBar.

Related

React error overlay - show for one error then throw a different one for calling code to handle

I currently have a react app where there are some async functions dealing with redux actions. All these actions are wrapped in this Act function to ensure that any unexpected errors are logged and a more user friendly error is sent to the UI code which then displays it to the user.
export function Act<R>(
callback: (dispatch: Dispatch, getState: () => ReduxState) => Promise<R>
): typeof callback {
return async (dispatch, getState) => {
try {
// if the callback works fine, just return it's value
return await callback(dispatch, getState);
} catch (e) {
if (e instanceof UserError) {
// we are expecting the action to throw these, although should still be logged
console.log(`async callback threw user error: ${e}`);
// propogate to UI code.
throw e;
} else {
// Some exception happened that wasn't expected, log a traceback of the error
console.trace(`Error in Async Action: ${e}`);
// HERE IS WHERE I WANT TO IMPROVE ^
// since the error thrown will likely be shown to the user just show a generic message
throw new UserError('something went wrong');
}
}
};
}
This is working fine although sometimes console.trace isn't enough for me to realize something went wrong that I wasn't expecting, either because the error never makes it's way to the UI or
What I would really like to do is when an unexpected error is thrown in these actions and dev mode is on it would show the error overlay that would be shown if this was a floating promise
I tried using reportRuntimeError from react-error-overlay but I obviously didn't import it correctly since it was logged as undefined:
import { reportRuntimeError } from 'react-error-overlay'; // didn't work and couldn't find type definitions for the module
I tried npm install #types/react-error-overlay which wasn't able to find type definitions for that module and I'm not clear on whether that is the right place to even be trying to do this.
Is there a way to show the error that was originally thrown and then return a different one to be handled by the UI code?
Realized about half way through writing my question that React shows the overlay for Promises that throw an error that are never handled, so I just had to make a promise that happens to throw the error I want to show and not handle it anywhere:
else {
// This gets logged as an Unhandled Promise Rejection
// so React will show the overlay for it as well.
void Promise.reject(e);
throw new UserError(fallbackErrorMessage);
}

Redirecting from getInitalProps in React/Next.js

I am using React and Next.js and trying to redirect a user from a page when the data for that page is not available using Router.push('/another-page').
To do this I am checking for a status code in getInitalProps and applying a conditional. It looks like this:
const statusCode = action.currentArticle ? 200 : 404
if (isServer) res.statusCode = statusCode
if (statusCode === 404) {
Router.push('/')
}
The status code is being set properly and it makes it inside the conditional, at which point I am greeted with this error: No router instance found. You should only use "next/router" inside the client side of your app.
Actually, I am getting the same error no matter WHERE in the component's lifecycle events I try to redirect, and am getting little info online about this error.
The pattern of redirecting from getInitalProps can be seen in this next.js wiki: HERE
Any ideas on why this error is occurring or how to fix it are much appreciated ;)
With Next.js (and any universal react rendering) your code is executing in two different environments. First in Node (on the server) and then in a browser. Next does some work to provide unified functions that run in both these environments but they're very different. Next can't and doesn't keep this from you. It seems like you just loaded a page in your browser but here's a little more detail on what's really going on…
On the client/browser:
Type url in the address bar (localhost:3000 or whatever), press enter.
GET request goes out to the server (Node).
On the server/Node:
GET request comes in.
Node gives you a request and a response object.
Maybe you have some Express routing/middleware.
At some point Next's render() function is called with the request and response objects.
Next runs getInitialProps and passes in the request/response.
React renderToString() is called which calls the following React lifecycle methods:
constructor()
componentWillMount()
render()
React creates a string of HTML that gets sent to the client.
^ This is Node. You can't access window, you don't have fetch, and you can't use the Next Router. Those are browser things.
Back on the client:
HTML is downloaded and rendering begins.
Links to js/css files in the HTML are downloaded/run.
This includes js code compiled by Next.
React render() is run which associates the downloaded HTML (the DOM) with a React virtual DOM. The following React lifecycle methods will run:
constructor()
componentWillMount()
render()
componentDidMount()
All other lifecycle methods (updates) will run when props/state change.
^ This is the browser. You have window, you have fetch, you can use the Next Router. Now you don't have the Node request/response but that seems to catch people up less.
Ref: Component lifecycle
The way works like #Shi said, but there is not server in getInitialProps. Instead of that, there should check window:
getInitialProps({res}){
if(typeof window === 'undefined')
res.redirect('/');
else
Router.push('/');
}
You can redirect from getInitialProps() like this:
import Router from 'next/router'
static getInitialProps = (ctx) => {
// On server
if(typeof window === 'undefined'){
res.writeHead(302, {location: '/dashboard'})
res.end()
} else {
// On client
Router.push('/dashboard')
}
return {}
}
See https://github.com/zeit/next.js/issues/649
next/router is not available on the server that's way you get an error saying that router not found, next/router can only be used on the client side.
For you to redirect a user inside getInitialProps in the server you can use:
getInitialProps({server,res}){
if(server)
res.redirect('/');
else
Router.push('/');
}
To make sure the page never render, we need to add await new Promise(() => {}) to end. The promise no needed resolve anything.
Home.getInitialProps = async ({res}) => {
if(res) {
res.writeHead(302, {location: '/dashboard'});
res.end();
} else {
// window.location.href = '/dashboard';
// Or with SPA redirect
Router.push('/dashboard');
}
await new Promise(() => {});
return {}
}
I found this https://www.npmjs.com/package/nextjs-redirect to be very simple and solved the issue for both client and server side.
pages/donate.js
import redirect from 'nextjs-redirect'
export default redirect('https://paypal.me')

React Redux correct way to handle error / success messages unique to each component

Problem outline
Below is a standard way for dispatching error / success messages, at least that's what the tutorials recommend:
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', success: 'yay', response: { ... } }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }
However, the problem I'm experiencing with this, is that these error / success messages will show inside EVERY component listening to the specific reducer that's handling these actions.
Example scenario:
Image you have a todo application which has three actions add, edit and delete however all three of those actions are performed from separate pages in terms of UI. And they're all handled by the same todo reducer.
Also all components that provide UI for triggering these actions are listening to this todo reducer via mapStateToProps to receive todo changes (not relevant in this example) and success & error messages:
function mapStateToProps(state) {
return {
success: state.todo.success,
error: state.todo.error,
};
}
{!!this.props.success && <p>{this.props.success}</p>}
{!!this.props.error && <p>{this.props.error}</p>}
Issue with above
The problem is that there’s no way to distinguish between add todo errors, edit todo errors and delete todo errors.
So if add todo triggers an error this error will now also show up next to edit todo and delete todo, because all 3 actions are listening for the same: state.todo.error.
Below are the 2 solutions I came up with. Any feedback / critique as well as new suggestions would be more than welcome.
What I came up with so far
1: Global status reducer that handles errors / successes of each action
Setup a global status reducer that handles errors / successes of each action - keys on the reducer would consist of small specialised sub-reducers unique to each action, i.e.:
const status = combineReducers({
add_todo, // handles error/success for add_todo
edit_todo, // handles error/success for edit_todo
delete_todo, // handles error/success for delete_todo
…
update_bio, // handles error/success for update_bio
etc...
});
Then you can simply listen for these success / error messages in your components and render them as follows:
function mapStateToProps(state) {
return {
error: state.status.add_todo.error,
success: state.status.add_todo.success
};
}
{!!this.props.error && <p>{this.props.error}</p>}
{!!this.props.success && <p>{this.props.success}</p>}
etc...
function mapStateToProps(state) {
return {
error: state.status.edit_todo.error,
success: state.status.edit_todo.success
};
}
{!!this.props.error && <p>{this.props.error}</p>}
{!!this.props.success && <p>{this.props.success}</p>}
The downside of this is that it would force you to create a sub-reducer for each action that an application provides, which I'm not sure is a correct way of doing it?
2: Use locally unique error / success keys inside of each reducer that manages more than a single action
i.e. we could modify the todo reducer as follows:
ADD_TODO_FAILED:
return {...state, error_add_todo: action.error }
EDIT_TODO_FAILED:
return {...state, error_edit_todo: action.error }
DELETE_TODO_FAILED:
return {...state, error_delete_todo: action.error }
Then you can simply listen for these success / error messages in your components and render them as follows:
function mapStateToProps(state) {
return {
error: state.todo.error_add_todo,
success: state.todo.success_add_todo
};
}
{!!this.props.error && <p>{this.props.error}</p>}
{!!this.props.success && <p>{this.props.success}</p>}
etc...
function mapStateToProps(state) {
return {
error: state.todo.error_edit_todo,
success: state.todo.success_edit_todo
};
}
{!!this.props.error && <p>{this.props.error}</p>}
{!!this.props.success && <p>{this.props.success}</p>}
The downside of this is would be that you would have to make sure that each error / success key inside of a given reducer is locally unique inside of that reducer.
And it would also make referring to keys inside of stateMapToProps more verbose as instead of saying state.todo.error your would have to refer to that error by using the specific name that you gave it.
Question
So my question is, based on all of the above, did I completely miss something out of the equation in my above observations, or were my observations correct in which case what is the standard way of achieving this desired outcome?
And are the 2 solutions I proposed legit or too naive for a real world application?
Thanks a lot!
1.
Your first option is problematic, as each of your sub reducers would not have access to the global state (list of todos). I would like to know how you would code each of the sub reducers...
2.
Your second option is relatively good. However, it shows a common red flag: when you start naming variables like state.todo.error_add_todo and state.todo.error_edit_todo, you should usually want to use an object instead, ending up with state.todo.error.add_todo.
Actually I would even go further and group error and success into status.
This means that you would reshape your state to have this shape:
{
todos:[{...todo1}, {...todo2}, ...todos]
status:{
add_todo: {success: true, error: false, message: "yay"}
remove_todo: {success: false, error: true, message: "nooo"}
}
}
So this would be the way I would fix your code. However, this seems a bit overkill to me, as this state shape allows you to represent and store any combination of simultaneous errors from different reducers, which is not trivial to display to the user.
In the case of error display, such precision is rarely needed, and more often than not, you would just imperatively trigger a popup or a toaster message when your action fails.
Also, you should probably have a look at redux-actions and redux-promise-middleware, this is how I handle my errors.

trying to understand .apply(undefined, arguments))

I've been developing an app in react.
Just a Simple app, it has a feature where I check toggle/toggle state for items inside a list.
At utils.js I have
export const partial = (fn, ...args) => fn.bind(null, ...args)
const _pipe = (f, g) => (...args) => g(f(...args))
export const pipe = (...fns) => fns.reduce(_pipe)
but the there is a problem in App.js, when using the utils:
const getToggledTodo = pipe(findById, toggleCompleted)
the helpers' imports seem fine:
import {pipe, partial} from './lib/utils'
import {addTodo, generateId, findById, toggleCompleted,
updateTodo, removeTodo, filterTodos} from './lib/todoHelpers'
Still , the app complains
Uncaught TypeError: Cannot read property 'find' of undefined
doing console I get:
f2: function () {
return g(f.apply(undefined, arguments));
}
I looked at:
at findById (todoHelpers.js:15)
at utils.js:10
at Object.executeOnChange (LinkedValueUtils.js:132)
and seems to me the undefined is coming from linkedValue file at last line:
executeOnChange: function (inputProps, event) {
if (inputProps.valueLink) {
_assertValueLink(inputProps);
return inputProps.valueLink.requestChange(event.target.value);
} else if (inputProps.checkedLink) {
_assertCheckedLink(inputProps);
return inputProps.checkedLink.requestChange(event.target.checked);
} else if (inputProps.onChange) {
return inputProps.onChange.call(undefined, event);
}
}
};
Not sure how .apply and .call relate to each other here, and seems to me that I'm missing an argument somewhere.
The final objective is to update of state complete/not complete in the db, plus an message in the UI saying that in fact the item has been updated.
Fun fact: if I hard code some similar structured object in App.js and use it in memory to change state, the error does not show... o_O.
It only appears when trying to connect to a 'db', which is still of course a mock. Don't know if its related but I think is worth mentioning.
Using json-server to mock db objects.
So my question is: how to debug this error? Can someone help me understand a bit how apply and call relate to this error.
Any pointers in the right direction would be very helpful and much appreciated.

How should we handle an error while getting initial state from store in getDataFromTree

What I am trying to do
Render a page server side with data from our graphql server with errors.
Context
Our GraphQL server speaks to multiple APIs and returns a graphQL response which is handled by Apollo UI.
Certain GraphQL errors are not considered critical, and we want to still render a page with it. E.g.
data: {
thing: {
id: "1234"
name: "Thing"
nonCriticalData: null
},
error: {
graphQLErrors: [
path: ['thing', 'nonCriticalData']
]
}
}
With the above response we get from Apollo, we want to still render a page with thing. Looking at the implementation of getDataFromTree here, any error will get thrown, without the rest of the data.
How we plan to handle our errors
Here is a snippet of how we plan to handle our code:
getDataFromTree(app)
.then(() => {})
.catch(error => {
const content = ReactDOMServer.renderToString(app);
const { data } = client.getInitialState();
console.log("data", data); <-------------- We need data here
console.log("error", error); <———————— errors are here
// render page with data
});
Please let me know if there are ways around this, or whether I have missed
something.
Extra questions:
Should getDataFromTree even throw errors? It feels like the responsibility should lie with the consumer.

Categories

Resources