javascriptreactjsrecoiljs

React recoil, overlay with callback and try finally


I want to render overlay on the long running operations. Consider I have the following code

let spinnerState = useRecoilValue(overlayState);

return <BrowserRouter>
            <Spin indicator={<LoadingOutlined />} spinning={spinnerState.shown} tip={spinnerState.content}>.........</BrowserRouter>

What I do in different components

const [, setOverlayState] = useRecoilState(overlayState);

const onButtonWithLongRunningOpClick = async () => {
    Modal.destroyAll();
    setOverlayState({
        shown: true,
        content: text
    });
    try {
        await myApi.post({something});
    } finally {
        setOverlayState(overlayStateDefault);
    }
}

How can I refactor this to use such construction that I have in this onbuttonclick callback? I tried to move it to the separate function, but you cannot use hooks outside of react component. It's frustrating for me to write these try ... finally every time. What I basically want is something like

await withOverlay(async () => await myApi.post({something}), 'Text to show during overlay');

Solution

  • create a custom hook that handles the logic for showing and hiding the overlay.

    import { useRecoilState } from 'recoil';
    
    const useOverlay = () => {
      const [, setOverlayState] = useRecoilState(overlayState);
    
      const withOverlay = async (fn: () => Promise<void>, content: string) => {
        setOverlayState({ shown: true, content });
        try {
          await fn();
        } finally {
          setOverlayState(overlayStateDefault);
        }
      };
    
      return withOverlay;
    };
    

    You can then use the useOverlay hook in your components

    import { useOverlay } from './useOverlay';
    
    const Component = () => {
      const withOverlay = useOverlay();
    
      const onButtonWithLongRunningOpClick = async () => {
        await withOverlay(async () => await myApi.post({ something }), 'Text to show during overlay');
      };
    
      return <button onClick={onButtonWithLongRunningOpClick}>Click me</button>;
    };