How to Implement a Global Error Handling Popup in React Native with React Query
Tags: reactnative, mobile, javascript
Published: 2024-05-19 19:00:15

Handling errors gracefully in a React Native application is essential for providing a smooth user experience. One effective approach is to implement a global error handling popup that informs users of issues and allows them to retry failed actions. In this blog post, we will walk through how to achieve this using React Native and React Query.

### Setting Up Your Project
Ensure you have a React Native project set up. If not, you can create one using [Expo](https://docs.expo.dev/tutorial/create-your-first-app/).

### Creating the PopupController
PopupController will manage the visibility of the GlobalPopup. This controller can be used by other components to show or hide the popup.
```ts
export interface PopupRef {
  show: (failedQuery: QueryKey) => void;
  hide: () => void;
}

export default class PopupController {
  static popupRef: MutableRefObject<PopupRef>;

  static setGlobalPopupRef = (ref: MutableRefObject<PopupRef>) => {
    this.popupRef = ref;
  };

  static showGlobalPopup = (failedQuery: QueryKey) => {
    this.popupRef.current?.show(failedQuery);
  };

  static hideGlobalPopup = () => {
    this.popupRef.current?.hide();
  };
}
```

### Creating the GlobalPopup Component
The GlobalPopup component will display error messages and provide an option to retry failed actions. It uses useLayoutEffect to set the ref of GlobalPopup in the popupController. It also uses useImperativeHandle to customise the handle exposed as a ref, allowing it to show and hide the popup.
```tsx
const GlobalPopup = () => {
  const [visible, setVisible] = useState(false);
  const [failedQueries, setFailedQueries] = useState<QueryKey[]>([]);
  const queryClient = useQueryClient();
  const popupRef = useRef<PopupRef>();
  
  // Using useLayoutEffect to set the ref of GlobalPopup in popupController
  useLayoutEffect(() => {
    PopupController.setGlobalPopupRef(popupRef as MutableRefObject<PopupRef>);
  }, []);

  // Using useImperativeHandle to customize the handle exposed as a ref
  useImperativeHandle(
    popupRef,
    () => ({
      show: (failedQuery: QueryKey) => {
        setVisible(true);
        if (!failedQueries.includes(failedQuery)) {
          setFailedQueries([...failedQueries, failedQuery]);
        }
      },
      hide: () => {
        setVisible(false);
        setFailedQueries([]);
      },
    }),
    [failedQueries]
  );

  const handleTryAgain = () => {
    failedQueries.forEach(async (query) => {
      await queryClient.invalidateQueries({ queryKey: query });
    });
    PopupController.hideGlobalPopup();
  };

  return (
    <Modal visible={visible}>
      <View>
        <Text>This is a global error popup</Text>
        <Button onPress={handleTryAgain} title="Try Again" />
      </View>
    </Modal>
  );
};
```
### Integrating GlobalPopup in the App Component
Now, integrate the GlobalPopup in the top-level component of your application and set up the React Query client to handle errors globally.
```tsx
const queryClient = new QueryClient({
  queryCache: new QueryCache({
    // Overwriting queryClient to handle errors globally
    onError: (_, query) => {
      PopupController.showGlobalPopup(query.queryKey);
    },
  }),
});
```
### How it works
- When any component in the application makes an API call using useQuery and it fails, the configured onError handler in App.tsx is triggered.
- The onError handler calls PopupController.showGlobalPopup with the failed query key (e.g., ["key"]).
- The show function in the popup reference object updates the visibility state of GlobalPopup and adds the failed query key to the internal list. 
- The global error popup becomes visible, displaying a generic error message and a "Try Again" button.
- Clicking "Try Again" triggers handleTryAgain in the popup. This function iterates through the failed queries, invalidates them using queryClient, and calls PopupController.hideGlobalPopup to hide the popup.
- React Query refetches the invalidated queries, potentially resolving the error.

By incorporating these enhancements, you can create a robust and informative error handling experience for your React Native applications.

Sample project with global error handling implementation: [github](https://github.com/rupeshghosh10/global-error-popup)