ignite-bowser
ignite-bowser copied to clipboard
Global error handling use MST
Hello everyone!
Iβve been looking for a solution for a very long time, I canβt find it anywhere. How to make a global error handler from the api server response to show the user a modal window use MST or something else? Or for example, if the token has expired redirect to the authorization window, if the answer 500 came to show the Houston we have problems screen. depending on server response.
a single handler that will consider different cases and show info windows
ππ»
Very interested in this topic as well. Maybe we could start by sharing our way to do it or our ideas? That would open up the discussion.
Hi! I don't have a repo example, but this is the rough code for what I've done for this in another project, in ignite-bowser's file structure:
dialog-store.ts
export const DialogStoreModel = types
.model("DialogStore")
.props({
dialog,
})
.extend(withEnvironment)
.views(self => ({}))
.actions(self => ({
setDialog: flow(function * (dialog) {
self.dialog = dialog
}),
})
.actions(self => ({
initializeStore: flow(function * () {
self.environment.setDialog = dialog => self.setDialog(dialog)
}),
}))
in setup-root-store.ts
Object.keys(rootStore).forEach(key => {
if (rootStore[key] && typeof rootStore[key].initializeStore === 'function') {
rootStore[key].initializeStore()
}
})
environment.ts
export class Environment {
constructor() {
this.api = new Api({
// this is a made up constructor! Whatever you use, you can set up your tooling here to do this on error
onError: (error) => {
if (this.onError) {
this.setDialog(errorToDialog(error))
}
}
})
api: Api
setDialog: (dialog: any) => void
}
app-wrapper.ts
export const AppWrapper: React.FunctionComponent<AppWrapperScreenProps> = observer(props => {
const nextScreen = React.useMemo(() => () => props.navigation.navigate("demo"), [
props.navigation,
])
const { dialogStore } = useStores()
return (
<View>
<SomeContent />
{dialogStore.dialog}
</View>
)
}
The setup works like this:
- the environment is created in
setup-root-store.ts
- it has a null (for now)
onError
function - the API is initialized with all requests calling
onError
if there is an error - when the
dialogStore
is created usingwithEnvironment
, callinitializeStore
duringsetup-root-store
so thatenvironment
'sonError
is hooked up todialogStore
- use the
dialogStore
on some app wrapper component that always shows thedialogStore
's dialog if it has one
That way, the app wrapper is observing the dialogStore
's dialog, and all requests made by the API library in environment
are hooked up to dialogStore
.
I'm sure someone who's more MST-savvy can make this even cleaner somehow!
Example repo incoming π
Edit: example repo at https://github.com/jksaunders/ignite-bowser-339
^ In this repo, the mobile app has a valid API request button (no errors shown) and an invalid API request button (error message briefly shown). It's all from a single handler, observable anywhere!
for your ex, catch the token expires, please check api-problem.ts, and here how can i handle the token expires:
`export function getErrorMessage(data: any): string {
try {
if (data.message != null) {
return data.message
} else {
return translate('errors.serverError')
}
} catch (e) {
return translate('errors.serverError')
}
}
const doExpire = async (msg: string) => {
storage.remove(common.TOKEN)
rootStore.toast.show(msg)
await delay(500)
rootStore.auth.setAuth(false)
RootNavigation.resetRoot({ routes: [] })
rootStore.resetAll()
}
export function getGeneralApiProblem(response: ApiResponse
return null } `
Hi! I don't have a repo example, but this is the rough code for what I've done for this in another project, in ignite-bowser's file structure:
dialog-store.ts
export const DialogStoreModel = types .model("DialogStore") .props({ dialog, }) .extend(withEnvironment) .views(self => ({})) .actions(self => ({ setDialog: flow(function * (dialog) { self.dialog = dialog }), }) .actions(self => ({ initializeStore: flow(function * () { self.environment.setDialog = dialog => self.setDialog(dialog) }), }))
in
setup-root-store.ts
Object.keys(rootStore).forEach(key => { if (rootStore[key] && typeof rootStore[key].initializeStore === 'function') { rootStore[key].initializeStore() } })
environment.ts
export class Environment { constructor() { this.api = new Api({ // this is a made up constructor! Whatever you use, you can set up your tooling here to do this on error onError: (error) => { if (this.onError) { this.setDialog(errorToDialog(error)) } } }) api: Api setDialog: (dialog: any) => void }
app-wrapper.ts
export const AppWrapper: React.FunctionComponent<AppWrapperScreenProps> = observer(props => { const nextScreen = React.useMemo(() => () => props.navigation.navigate("demo"), [ props.navigation, ]) const { dialogStore } = useStores() return ( <View> <SomeContent /> {dialogStore.dialog} </View> ) }
The setup works like this:
- the environment is created in
setup-root-store.ts
- it has a null (for now)
onError
function- the API is initialized with all requests calling
onError
if there is an error- when the
dialogStore
is created usingwithEnvironment
, callinitializeStore
duringsetup-root-store
so thatenvironment
'sonError
is hooked up todialogStore
- use the
dialogStore
on some app wrapper component that always shows thedialogStore
's dialog if it has oneThat way, the app wrapper is observing the
dialogStore
's dialog, and all requests made by the API library inenvironment
are hooked up todialogStore
.I'm sure someone who's more MST-savvy can make this even cleaner somehow!
Example repo incoming π
Edit: example repo at https://github.com/jksaunders/ignite-bowser-339
^ In this repo, the mobile app has a valid API request button (no errors shown) and an invalid API request button (error message briefly shown). It's all from a single handler, observable anywhere!
Thank you so much! It really helped
thanks for sharing \o/, another suggestion here instead of changing the logic to use axiosInstance
just to catch the onError
, we can simply add a monitor to sauceApi, and filter by response.problem
,
import * as SecureStorage from "@utils/secure-storage"
import { Api } from "../services/api"
import { ACCESS_TOKEN_KEY } from "@utils/constants"
/**
* The environment is a place where services and shared dependencies between
* models live. They are made available to every model via dependency injection.
*/
export class Environment {
constructor() {
this.api = new Api()
}
async setup() {
await this.api.setup()
this.api.apisauce.addMonitor((response) => {
if (this.setResponse) {
this.setResponse(response)
}
})
}
/**
* Our api.
*/
api: Api
setResponse: (response: any) => void
}