The Widlarz Group Blog

Errors handling with RN Notificated

August 09, 2022

errors handling

react native

mobile

react native notificated

axios

Introduction

How and why? That’s the question that came to my mind years ago when I first faced error handling in my app. Back then, it ended with “I’ll do it later” and doing absolutely nothing.

However, sooner or later comes a moment when, an error that you left behind punches you in the fact and says: ‘You will never leave me again’. That’s when you realize that you can no longer sweep it under the rung. You have to handle it somehow. But how? What tools to use? In this article I will try to answer this question and demonstrate how how to use react-native-notificated to inform users about errors.

You can find the repository with the code used in the article here.

Content

Where to look for errors?

Before we move on and talk about ways of handling individual errors, let’s think what kind of error categories we have. In my opinion, the most obvious and actually, the best one, is division according to the error ‘location’. Below you’ll find three of the most common locations:

  • Component rendering errors
  • Event handlers and asynchronous callbacks
  • Server-side rendering

Let’s see how the app will behave in debug mode without any errors handled:

Unhandle errors

Clearly, the app is throwing errors or showing a loader without informing the user about what’s going on. It would look even worse in release mode. The app would crash or freeze and that’s the opposite from what we want, right? So, let’s not waste our time any more and take care of each scenario individually.

Component rendering errors

The first group of errors appears in the component render method. Let’s handle them with the react-error-boundary package, where the documentation says:

This component provides a simple and reusable wrapper that you can use to wrap around your components. Any rendering errors in your components hierarchy can then be gracefully handled.

Let’s try to implement that. First, we need to create a simple FallbackComponent, which will be displayed when there’s an error:

export const FallbackComponent = ({ error, resetError }) => {
  return (
    <Container>
      <Icon source={require('../assets/error.png')} />
      <Title>Something happened!</Title>
      <Description>{error.toString()}</Description>
      <Button title={'Try again'} onPress={() => resetError} />
    </Container>
  )
}

// src/components/FallbackComponent.js

The next step is to wrap the component that could have errors with ErrorBoundary and to pass FallbackComponent and onError handler to it:

export const BoundaryErrorsScreen = () => {
  const [isErrorComponentVisible, setIsErrorComponentVisible] = useState(false)

  const onPress = () => setIsErrorComponentVisible(true)
  const onError = (error) => console.log(error)

  return (
    <ErrorBoundary onError={onError} FallbackComponent={FallbackComponent}>
      <InvokeErrorButton onPress={onPress} />
      {isErrorComponentVisible && <ComponentWithError />}
    </ErrorBoundary>
  )
}

// src/screens/BoundaryErrorsScreen.js

If we run our project again, we will see that the app isn’t showing a render error anymore. Instead, we’ll see FallbackComponent that we created before.

BoundaryErrorsScreen errors handled

Event handlers and asynchronous callbacks errors

This group of errors can’t be handled with error boundaries, which is why we need a different approach. The easiest method (and overall the best) is to handle it with a simple try catch block, built in JS.

Here’s what the implementation will look like:

export const EventErrorsScreen = () => {
  const { navigate } = useNavigation()

  const throwError = () => {
    try {
      throw new Error('Error description')
    } catch (error) {
      console.log(error)
    }
  }

  return (
    <Container>
      <Button title="Throw Event Error" onPress={throwError} />
      <Button title="Go to SSR Errors" onPress={() => navigate('SSR Errors')} />
      <Button title="Go to Boundary Errors" onPress={() => navigate('Boundary Errors')} />
    </Container>
  )
}

// src/screens/EventErrorsScreen

Nice, we consoled event error so it doesn’t appear on the screen anymore.

EventErrorsScreen errors handled

Server-side rendering errors

The last type of error occurs when the screen can’t be rendered due to server bugs. In response, we get the error with a proper status code from the ‘fetch’ feature. Based on this code, we can handle the error with a reusable axios interceptor.

Let’s jump straight to the code. First, let’s create a AxiosInterceptorProvider component. Inside it, we need to set up our interceptor:

export const AxiosInterceptorProvider = ({ children }) => {
  useEffect(() => {
    axios.defaults.baseURL = 'https://rickandmortyapi.com/api'

    const resInterceptor = (response) => response
    const errInterceptor = (error) => {
      console.log({
        error: {
          status: error.response.status,
          message: error.message,
        },
      })
      return error
    }

    const interceptor = axios.interceptors.response.use(resInterceptor, errInterceptor)

    return () => axios.interceptors.response.eject(interceptor)
  }, [])

  return children
}

// src/providers/axiosInterceptorProvider

Now, let’s wrap our app with AxiosInterceptorProvider:

export const RootProvider = ({ children }) => {
  return (
    <AxiosInterceptorProvider>
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    </AxiosInterceptorProvider>
  )
}

// src/providers/rootProvider

That’s all. Each time we call the axios fetch method, our interceptor will trigger and handle the possible errors.

AxiosInterceptorProvider errors handled

How do inform users about errors?

Given that console.logs are not meant to be seen by users and we usually get rid of them during release builds, we have to use some visual way to show feedback to users. For me, the best way is to show some kind of an in-app notification, like a box or a snack bar. This is exactly what we can do with react-native-notificated, which is our new library capable of doing such things! It’s fast and highly customizable, so I highly recommend you check it out!

Setting up react-native-notificated is fairly straightforward, as you can see below. Since we want to customize it too, we will create our own notification component which we will use to overwrite the default component. Feel free to look into the documentation for more details:

Let’s start with creating a basic notification component and styling it. This is how our future custom notification will look like:

export const CustomNotification = ({ customTitle, customDescription }) => {
  return (
    <Container>
      <Icon source={require('../assets/error.png')} />
      <Box>
        <Title>{customTitle}</Title>
        <Description>{customDescription}</Description>
      </Box>
    </Container>
  )
}

// src/components/CustomNotification

As mentioned before, react-native-notificated delivers nice notifications by default, but for this example we’ll customize them with CustomNotification component that we declared before. Let’s also assume that we want the notification to appear at the top for 5 seconds. We can do this with a simple config object like below:

const { NotificationsProvider } = createNotifications({
  variants: {
    customNotification: {
      component: CustomNotification,
      config: {
        notificationPosition: 'top',
        duration: 5000,
      },
    },
  },
})

export const NotifyProvider = ({ children }) => {
  return <NotificationsProvider>{children}</NotificationsProvider>
}

// src/providers/notifyProvider

Nice. But how about changing the animation a bit? Let’s say we want our notification to fade in and zoom in from the top of screen. To do that, we need to import one of the built-in animations and add it to the config. You can also declare your own animation from scratch, that’s how highly customizable our library is! Check the documentation for more details here:

// import { ZoomInDownZoomOutUp } from 'react-native-notificated'

const { NotificationsProvider } = createNotifications({
  variants: {
    customNotification: {
      component: CustomNotification,
      config: {
        notificationPosition: 'top',
        duration: 5000,
        animationConfig: ZoomInDownZoomOutUp,
      },
    },
  },
})
// src/providers/notifyProvider

Next step is to wrap our app with NotifyProvider:

export const RootProvider = ({ children }) => {
  return (
    <NotifyProvider>
      <AxiosInterceptorProvider>
        <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
      </AxiosInterceptorProvider>
    </NotifyProvider>
  )
}

// src/providers/rootProvider

Finally, let’s use our custom notification to replace console.log() in the application:

  • Component rendering errors
// const { notify } = useNotifications()

const onError = (error) => {
  notify('customNotification', {
    params: {
      customTitle: 'Boundary error',
      customDescription: error.message,
    },
  })
}

// src/screens/BoundaryErrorsScreen
  • Event handlers and asynchronous callbacks
// const { notify } = useNotifications()

const throwError = () => {
  try {
    throw new Error('Error description')
  } catch (error) {
    notify('customNotification', {
      params: {
        customTitle: 'Error title',
        customDescription: error.message,
      },
    })
  }
}

// src/screens/EventErrorsScreen
  • Server-side rendering
// const { notify } = useNotifications()

const errInterceptor = (error) => {
  if (error.response) {
    notify('customNotification', {
      params: {
        customTitle: `Response error status ${error.response.status}`,
        customDescription: error.message,
      },
    })
  }
  return error
}

// src/providers/axiosInterceptorProvider

That’s all. Now, thanks to react-native-notificated the user will know when the error occurs and what’s going on. He won’t be confused anymore. Let’s look at the behavior of our app:

Errors handle with notifications

Conclusion

That was simple, right? It only took us a few minutes and believe me when I say that it saved us a lot of headaches in the future. Error handling doesn’t have to be unpleasant at all. It all depends on the approach we use. In my opinion, the number of available tools is so large that everyone will find something suitable.

The tools that I presented in this article seem to be the most universal and sufficient method to handle erros in simple projects. In addition, react-native-notificated can be used not only to handle errors, but also to inform the user about everything that’s going on in your app. The same goes for axios, which you will usually use in your app anyway.

Of course, we can do much more than that. For example, in addition to handling errors, we can also monitor them using the Sentry library. Check out the Sentry documentation for more info.

I hope that after reading this article, you will have a different perspective on errors in your app, and you’ll never let them surprise you again!


Written by Maciej Szczepański.

Industries

  • Fintech
  • Health Care
  • E-commerce
  • Entertainment
  • Gambling
  • Telecommunication

Business models

  • Consultancy
  • Workshops
  • Outsourcing
  • Team Extension
  • Audit & Estimation

Technologies

  • iOS/Android
  • React/React Native
  • Node.js
  • TypeScript