JaktensTid
JaktensTid

Reputation: 330

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');

Upvotes: 3

Views: 476

Answers (2)

Dreamy Player
Dreamy Player

Reputation: 615

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>;
};

Upvotes: 0

sungryeol
sungryeol

Reputation: 4015

Solution

Write a custom hook that includes both UI and API. This pattern is widely used in a large app but I couldn't find the name yet.

// lib/buttonUi.js
const useOverlay = () => {
  const [loading, setLoading] = useState(false);
  return {loading, setLoading, spinnerShow: loading };
}

export const useButton = () => {
  const overlay = useOverlay();
  const someOp = async () => {
    overlay.setLoading(true);
    await doSomeOp();
    /* ... */
    overlay.setLoading(false);
  }
  return {someOp, ...overlay}
}

// components/ButtonComponent.jsx
import { useButton } from 'lib/buttonUi';

const ButtonComponent = () => {
  const {spinnerShow, someOp} = useButton();
  return <button onClick={someOp}>
    <Spinner show={spinnerShow} />
  </button>
}

export default ButtonComponent;

Upvotes: 2

Related Questions