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:
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
To get rid of your infinite loop, simply use an empty dependency array like so: const [count, setCount] = useState(0); //only update the value of 'count' when component is first mounted useEffect(() => { setCount((count) => count + 1); }, []); This will tell React to run useEffect on the first render.
You can't use a hook inside another hook because it breaks the rule Call Hooks from React function components and the function you pass to useEffect is a regular javascript function. What you can do is call a hook inside another custom hook.
Changing state will always cause a re-render. By default, useEffect always runs after render has run. This means if you don't include a dependency array when using useEffect to fetch data, and use useState to display it, you will always trigger another render after useEffect runs.
To make your useEffect run only once, pass an empty array [] as the second argument, as seen in the revised snippet below. You could pass in any number of values into the array and useEffect will only run when any one of the values change.
That's an infinite loop. it generates an infinite loop of component re-renderings. After initial rendering, useEffect () executes the side-effect callback that updates the state. The state update triggers re-rendering.
If you start using React-Hooks, and your component might need a life cycle method at some point. And, that is when you start using useEffect () (a.k.a Effect Hook ). Then boom!!, you have encountered an infinite loop behavior, and you have no idea why the hell is that.
An efficient way to avoid the infinite loop is to properly manage the hook dependencies — control when exactly the side-effect should run. useEffect(() => { // No infinite loop setState(count + 1); }, [whenToUpdateValue]); Alternatively, you can also use a reference. Updating a reference doesn’t trigger a re-rendering:
If you are building a custom hook, you can sometimes cause an infinite loop with default as follows function useMyBadHook (values = {}) { useEffect ( ()=> { /* This runs every render, if values is undefined */ }, [values] ) } The fix is to use the same object instead of creating a new one on every function call:
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:
Please Let me know if any explanation is needed
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With