两次调用useReducer动作,状态永远不会在包含的钩子中更新

时间:2020-08-30 17:35:19

标签: reactjs react-hooks use-reducer

我正在创建一个自定义钩子,可在整个应用程序中重复使用,但结果出乎意料 从代码中的注释中可以看到,操作已成功调度,并具有预期的有效负载值,我传递给它们的任何代码都可以像console.log一样成功运行,但是它们调度了两次,并且在尝试获取状态值时我没有这样做在分派了一些动作后没有得到预期的状态,我得到了默认的状态。

import { useReducer, useCallback, useState } from "react";
import { functions } from "../base";
const initialState = {
  to: [],
  cc: [],
  bcc: [],
  data: {},
  subject: "",
};
const SET_RECIEPENTS = "SET_RECIEPENTS";
const ADD_RECIEPENT = "ADD_RECIEPENT";
const SET_SUBJECT = "SET_SUBJECT";
const SET_CC = "SET_CC";
const ADD_CC = "ADD_CC";
const SET_BCC = "SET_BCC";
const ADD_BCC = "ADD_BCC";
const SET_DATA = "SET_DATA";
const RESET = "RESET";
const emailReducer = (state, action) => {
  console.log("prevState: ", state); // logs state
  console.log("action: ", action); // called twice with the expected type and payload and triggers state change
  switch (action.type) {
    case SET_RECIEPENTS:
      if (!action.reciepents instanceof Array) {
        throw new Error("reciepents must be an Array");
      }
      return {
        ...state,
        to: [...action.reciepents],
      };
    case ADD_RECIEPENT:
      if (typeof action.reciepent !== "string" || action.reciepent === "") {
        throw new Error("the reciepent must be a string");
      }
      return { ...state, to: [...state.to, action.reciepent] };
    case SET_SUBJECT:
      if (typeof action.subject !== "string" || action.subject === "") {
        throw new Error("Subject must be a string");
      }
      return {
        ...state,
        subject: action.subject,
      };
    case SET_CC:
      if (!action.cc instanceof Array) throw new Error("CC must be an Array");
      return {
        ...state,
        cc: [...action.cc],
      };
    case ADD_CC:
      if (typeof action.cc !== "string" || action.cc === "") {
        throw new Error("the CC must be a string");
      }
      return {
        ...state,
        cc: [...state.cc, action.cc],
      };
    case SET_BCC:
      if (!action.bcc instanceof Array) throw new Error("BCC must be an Array");
      return {
        ...state,
        bcc: [...action.bcc],
      };
    case ADD_BCC:
      if (typeof action.bcc !== "string" || action.bcc === "") {
        throw new Error("the BCC must be a string");
      }
      return {
        ...state,
        bcc: [...state.bcc, action.bcc],
      };
    case SET_DATA:
      if (typeof action.data !== "object") {
        throw new Error("Data must be a object");
      }
      return {
        ...state,
        data: { ...action.data },
      };
    case RESET:
      return { ...initialState };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
};
const useSendEmail = (templateId, cb = function () {}) => {
  if (!templateId || templateId === "" || typeof templateId !== "string") {
    throw new Error("please provide a valid template id");
  }
  const [state, dispatch] = useReducer(emailReducer, initialState);
  const [error, setError] = useState(null);
  const { to } = state;
  const setReciepents = useCallback(
    (reciepents) => dispatch({ type: SET_RECIEPENTS, reciepents }),
    []
  );
  const addReciepent = useCallback(
    (reciepent) => dispatch({ type: ADD_RECIEPENT, reciepent }),
    []
  );
  const setSubject = useCallback(
    (subject) => dispatch({ type: SET_SUBJECT, subject }),
    []
  );
  const setCC = useCallback((cc) => dispatch({ type: SET_CC, cc }), []);
  const addCC = useCallback((cc) => dispatch({ type: ADD_CC, cc }), []);
  const setBCC = useCallback((bcc) => dispatch({ type: SET_BCC, bcc }), []);
  const addBCC = useCallback((bcc) => dispatch({ type: ADD_BCC, bcc }), []);
  const setData = useCallback((data) => dispatch({ type: SET_DATA, data }), []);
  const reset = useCallback(() => dispatch({ type: RESET }), []);
  const sendEmail = useCallback(() => {
     console.log({state}) // returns the default state
    if (to && to.length > 1) {
     console.log({state}) // never runs becuase there aren't emails in to []
      try {
        const sendEmailFunction = functions.httpsCallable().sendEmail;
        sendEmailFunction({ ...state, templateId })
          .then(() => {
            cb({ ...state });
          })
          .catch((err) => {
            setError(err);
          });
      } catch (err) {
        setError(err);
      }
    }
  }, []);
  return {
    setReciepents,
    addReciepent,
    setSubject,
    setCC,
    addCC,
    setBCC,
    addBCC,
    setData,
    reset,
    sendEmail,
    error,
  };
};
export default useSendEmail;

在Component上,我使用的挂钩代码是这样的

 const {
    sendEmail,
    error,
    addReciepent,
    addCC,
    addBCC,
    setSubject,
    setData,
  } = useSendEmail("ID", () => {
    setSucces(true);
  });

    const handleAddEmail = () => {
    if (email !== "") {
      setEmailsList((list) => [...list, email]);
      addReciepent(email);
      setEmail("");
    }
  };

  const handleAddCC = () => {
    if (cc !== "") {
      setCCList((list) => [...list, cc]);
      addCC(cc);
      setCCValue("");
    }
  };

  const handleAddBCC = () => {
    if (bcc !== "") {
      setBCCList((list) => [...list, bcc]);
      addBCC(bcc);
      setBCCValue("");
    }
  };

  const handleSubmit = async (event) => {
    event.preventDefault();
    console.log("Submit");
    setSubject(subject);
    setData({ name, place });
    sendEmail();
  };

我真的很想继续使用这种自定义钩子方法,但是我确实希望这些bug可以帮助到别人。 谢谢

1 个答案:

答案 0 :(得分:0)

我终于找到答案了,问题在于useCallback中的sendEmail函数,状态的值是第一个渲染值,它被记录下来,因为我没有在deps数组中添加任何deps来解决这个问题

 const sendEmail = useCallback(() => {
     console.log({state}) // returns the default state
    if (to && to.length > 1) {
     console.log({state}) // never runs becuase there aren't emails in to []
      try {
        const sendEmailFunction = functions.httpsCallable().sendEmail;
        sendEmailFunction({ ...state, templateId })
          .then(() => {
            cb({ ...state });
          })
          .catch((err) => {
            setError(err);
          });
      } catch (err) {
        setError(err);
      }
    }
  }, [state]);

或者只是不要为此功能使用useCallback