7

I am trying to create a custom hook to wrap about Notistack (https://github.com/iamhosseindhv/notistack), a library for snackbars.

My hook looks like this:

import { useCallback } from 'react';
import { useSnackbar as useNotistackSnackbar } from 'notistack';

import { SNACKBAR_TYPES } from '../constants/properties';

const useSnackbar = () => {
  const { enqueueSnackbar } = useNotistackSnackbar();

  const showSnackbarVariant = useCallback(
    ({ text, action_text, onActionClick, variant }) =>
      enqueueSnackbar(
        {
          variant,
          text,
          action_text,
          onActionClick,
        },
        { autoHideDuration: action_text && onActionClick ? 9000 : 4000 }
      ),
    [enqueueSnackbar]
  );

  return {
    showSuccessSnackbar: ({ text, action_text, onActionClick }) =>
      showSnackbarVariant({
        variant: SNACKBAR_TYPES.SUCCESS,
        text,
        action_text,
        onActionClick,
      }),
    showErrorSnackbar: ({ text, action_text, onActionClick }) =>
      showSnackbarVariant({
        variant: SNACKBAR_TYPES.ERROR,
        text,
        action_text,
        onActionClick,
      }),
    showWarningSnackbar: ({ text, action_text, onActionClick }) =>
      showSnackbarVariant({
        variant: SNACKBAR_TYPES.WARNING,
        text,
        action_text,
        onActionClick,
      }),
    showDownloadSnackbar: ({ text, action_text, onActionClick }) =>
      showSnackbarVariant({
        variant: SNACKBAR_TYPES.DOWNLOAD,
        text,
        action_text,
        onActionClick,
      }),
    showPlainSnackbar: ({ text, action_text, onActionClick }) =>
      showSnackbarVariant({
        variant: SNACKBAR_TYPES.PLAIN,
        text,
        action_text,
        onActionClick,
      }),
  };
};

export default useSnackbar;

I need to use it in 2 places:

  1. outside of useEffect (but still within the component)
  2. inside of useEffect

However, even if I just add it as just a dependency on useEffect, it causes the infinite loop inside useEffect:

export default function MyComponent() {
  const { showSuccessSnackbar, showErrorSnackbar } = useSnackbar();
  const { mutate: activateConnectedAccount } =
    useCustomHook({
      onSuccess: async () => {
        showSuccessSnackbar({
          text: 'Direct deposit has been enabled.',
        });
      },
      onError: () => {
        showErrorSnackbar({
          text: 'An error occurred. Please double check your bank information.',
        });
      },
    });

  useEffect(
    () => {
      activateConnectedAccount()
      console.log("yooo");
    },
    [
      // showSuccessSnackbar
    ]
  );

  return (
    <div>
      Foobar
    </div>
  );
}

Codesandbox link: If you comment in line 30, it will cause the browser to freeze because it keeps running that loop

https://codesandbox.io/s/currying-browser-5vomm?file=/src/MyComponent.js

2
  • 1
    The sandbox you posted is breaking can you please fix it Commented Aug 23, 2021 at 15:39
  • @SumanthMadishetty Sorry about that! Fixed Commented Aug 23, 2021 at 16:27

3 Answers 3

8
+100

You are returning anonymous functions from useSnackbar hook, which creates a new function every time a re-render happens

Using useCallback on showSnackbarVariant function does the trick for me

Please find the updated useSnackbar hook below

useSnackbar.js

import { useCallback, useMemo } from "react";
import { useSnackbar as useNotistackSnackbar } from "notistack";

const useSnackbar = () => {
  const { enqueueSnackbar } = useNotistackSnackbar();

  const showSnackbarVariant = useCallback(
    ({ text, action_text, onActionClick, variant }) =>
      enqueueSnackbar(
        {
          variant,
          text,
          action_text,
          onActionClick
        },
        { autoHideDuration: action_text && onActionClick ? 9000 : 4000 }
      ),
    [enqueueSnackbar]
  );

  const showSuccessSnackbar = useCallback(
    ({ text, action_text, onActionClick }) => {
      showSnackbarVariant({
        variant: "success",
        text,
        action_text,
        onActionClick
      });
    },
    [showSnackbarVariant]
  );

  return {
    showSuccessSnackbar,
    showErrorSnackbar: ({ text, action_text, onActionClick }) => {
      console.log("eee");
      showSnackbarVariant({
        variant: "error",
        text,
        action_text,
        onActionClick
      });
    }
  };
};

export default useSnackbar;

Please find sandbox for reference:

Edit gallant-wildflower-l5zy3

Please Let me know if any explanation is needed

Sign up to request clarification or add additional context in comments.

2 Comments

Wow I can't believe I missed that. Thanks so much!
glad, that i can help
2

you could save the result of your custom hook in a useState and then use this value in a useEffect to trigger your event at the right time.

 const { showSuccessSnackbar, showErrorSnackbar } = useSnackbar();
  const [isSuccess, setIsSuccess] = useState(false);
  const { mutate: activateConnectedAccount } = useCustomHook({
    onSuccess: async () => {
      showSuccessSnackbar({
        text: "Direct deposit has been enabled."
      });
      setIsSuccess(true);
    },
    onError: () => {
      showErrorSnackbar({
        text: "An error occurred. Please double check your bank information."
      });
    }
  });

  useEffect(() => {
    activateConnectedAccount();
  }, []);

  useEffect(() => {
    if (isSuccess) {
      console.log("yooo");
    }
  }, [isSuccess]);

Here is the sandbox

Although I don't really understand why you would need to do this, as you could already do whatever needs to be done in your onSuccess callback. Your useCustomHook could also contain a useEffect within its body if you want it to hold the logic.

As the previous answer says if you use a function/complex object as a dependency on useEffect without useCallback/useMemo, the shallow comparison will fail and React will re-run the useEffect on every render. If you pass primitive value, React is smart enough to re-run the useEffect only when that value changes.

I like to pass only primitive values as dependencies to avoid these issues of too many calls/ infinite loop

Comments

0

Wrap your showSuccessSnackbar in useCallback function and then pass it to useEffect.

const showSuccessSnackbarCallback = useCallback(() => {
    showSuccessSnackbar();
  }, []);

  useEffect(() => {
    activateConnectedAccount();
    console.log("yooo");
  }, [showSuccessSnackbarCallback]);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.