You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React:在useEffect中使用自定义Notistack封装hook触发无限循环如何解决?

问题触发原因

你封装的useSnackbar自定义Hook,虽然内部的showSnackbarVariant已经用useCallback做了引用缓存,但最终返回的对象中,showSuccessSnackbar/showErrorSnackbar等方法都是每次Hook调用时新创建的箭头函数,引用地址每次渲染都会变化。
当你把这类引用不稳定的方法加入useEffect依赖数组时,每次组件重渲染都会判定依赖发生变化,触发useEffect回调执行;而回调里的activateConnectedAccount调用又会触发组件重渲染,生成新的showSuccessSnackbar引用,最终形成无限循环。

可行修复方案

方案1:给useSnackbar返回的所有方法加缓存(推荐)

直接从Hook层面保证返回方法的引用稳定性,是最彻底的修复方式,两种实现可选:
第一种是给每个方法单独加useCallback:

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

  // 给每个返回方法加useCallback缓存,依赖只有稳定的showSnackbarVariant
  const showSuccessSnackbar = useCallback(({ text, action_text, onActionClick }) =>
    showSnackbarVariant({
      variant: SNACKBAR_TYPES.SUCCESS,
      text,
      action_text,
      onActionClick,
    }), [showSnackbarVariant])

  const showErrorSnackbar = useCallback(({ text, action_text, onActionClick }) =>
    showSnackbarVariant({
      variant: SNACKBAR_TYPES.ERROR,
      text,
      action_text,
      onActionClick,
    }), [showSnackbarVariant])

  const showWarningSnackbar = useCallback(({ text, action_text, onActionClick }) =>
    showSnackbarVariant({
      variant: SNACKBAR_TYPES.WARNING,
      text,
      action_text,
      onActionClick,
    }), [showSnackbarVariant])

  const showDownloadSnackbar = useCallback(({ text, action_text, onActionClick }) =>
    showSnackbarVariant({
      variant: SNACKBAR_TYPES.DOWNLOAD,
      text,
      action_text,
      onActionClick,
    }), [showSnackbarVariant])

  const showPlainSnackbar = useCallback(({ text, action_text, onActionClick }) =>
    showSnackbarVariant({
      variant: SNACKBAR_TYPES.PLAIN,
      text,
      action_text,
      onActionClick,
    }), [showSnackbarVariant])

  return {
    showSuccessSnackbar,
    showErrorSnackbar,
    showWarningSnackbar,
    showDownloadSnackbar,
    showPlainSnackbar
  };
};

export default useSnackbar;

第二种是用useMemo包裹整个返回对象,效果完全一致:

return useMemo(() => ({
  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 }),
  // 剩余方法省略
}), [showSnackbarVariant])

方案2:组件层用useRef持有方法引用(不用改Hook的临时方案)

如果不方便修改useSnackbar的源码,可以在调用侧用useRef存储方法,避免把不稳定的引用加入依赖:

export default function MyComponent() {
  const { showSuccessSnackbar, showErrorSnackbar } = useSnackbar();
  // 用ref存储方法引用,每次渲染更新ref的值
  const successSnackbarRef = useRef(showSuccessSnackbar);
  const errorSnackbarRef = useRef(showErrorSnackbar);
  useEffect(() => {
    successSnackbarRef.current = showSuccessSnackbar;
    errorSnackbarRef.current = showErrorSnackbar;
  })

  const { mutate: activateConnectedAccount } =
    useCustomHook({
      onSuccess: async () => {
        successSnackbarRef.current({
          text: 'Direct deposit has been enabled.',
        });
      },
      onError: () => {
        errorSnackbarRef.current({
          text: 'An error occurred. Please double check your bank information.',
        });
      },
    });

  useEffect(
    () => {
      activateConnectedAccount()
      console.log("yooo");
    },
    [activateConnectedAccount] // 不需要再加snackbar相关方法
  );

  return <div>Foobar</div>;
}

方案3:忽略依赖检查(不推荐)

如果确认showSuccessSnackbar的功能永远不会变化,可以手动加注释忽略依赖检查,但这种方式有潜在bug风险,只适合临时调试用:

useEffect(
  () => {
    activateConnectedAccount()
    console.log("yooo");
  },
  // eslint-disable-next-line react-hooks/exhaustive-deps
  []
);

内容的提问来源于stack exchange,提问作者bigpotato

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 13:48:01