在 React 中使用 useMemo() 钩子导致我的函数落后一步

Using the useMemo() hook in React is causing my function to be a step behind

我正在使用 useMemo 挂钩 return 并过滤一组项目。然后我有一个切换功能,可以切换一个项目是真还是假,然后将该项目发回 API 如果它是真或假,并将其添加到列表中。在使用 useReducer 钩子的函数中,数组落后了一步。例如,项目数组得到 returned 并且您切换它们是否正在销售,如果您切换为真,它们将被添加到 saleList 中,如果它们被切换为不销售,它们将被添加到 notSaleList .在函数中,saleList 长度将返回为 3,但它实际上是 4,然后你删除一个家使其成为 3,但它将 return 4。有人知道为什么会这样吗?

const homesReducer = (state, action) => {
  switch (action.type) {
    case 'FETCH_INIT':
      return {
        ...state,
        isLoading: true,
        isError: false,
      };
    case 'FETCH_SUCCESS':
      //action.payload to object
      const entities = action.payload.reduce((prev, next) => {
        return { ...prev, [next.Id]: next };
      }, {});
      return {
        ...state,
        isLoading: false,
        isError: false,
        homes: entities,
      };
    case 'FETCH_FAILURE':
      return {
        ...state,
        isLoading: false,
        isError: true,
      };
    case 'TOGGLE_SALE_HOME_INIT':
      return {
        ...state,
        homes: {
          ...state.homes,
          // ask Jenkins
          [action.payload]: {
            ...state.homes[action.payload],
            IsSaleHome: !state.homes[action.payload].IsSaleHome,
          },
        },
      };
    case 'TOGGLE_SALE_HOME_SUCCESS':
      return {
        ...state,
      };
    case 'TOGGLE_SALE_HOME_FAILURE':
      // TODO update back if failed
      return {
        ...state,
        homes: {
          ...state.homes,
          // ask Jenkins
          [action.payload]: {
            ...state.homes[action.payload],
            IsSaleHome: !state.homes[action.payload].IsSaleHome,
          },
        },
      };  
    default:
      return { ...state };
  }
};

const useOnDisplayApi = activeLotNumber => {
    const [state, dispatch] = useReducer(homesReducer, {
      isLoading: false,
      isError: false,
      homes: [],
      saleHomes: [],
     });

     const homes = useMemo(() => {
       return Object.keys(state.homes).map(id => {
       return state.homes[id];
     });
      }, [state.homes]);
     }

     const saleHomes = useMemo(() => {
       return homes.filter(home => {
       return home.IsSaleHome;
     });
     }, [homes]);

     const notSaleHomes = useMemo(() => {
       return homes.filter(home => {
       return !home.IsSaleHome && !home.IsSuggestedSaleHome;
      });
     }, [homes]);

    const toggleSaleHome = async (home, undo = true) => {
    dispatch({ type: 'TOGGLE_SALE_HOME_INIT', payload: home.Id });
    try {
      const didUpdate = await updateInventory(
        activeLotNumber,
        home.InventoryId,
        {
          InventoryId: home.InventoryId,
          IsSaleHome: !home.IsSaleHome,
        }
      );
      if (didUpdate == true) {
        dispatch({ type: 'TOGGLE_SALE_HOME_SUCCESS' });
      } 
      else {
          dispatch({ type: 'TOGGLE_SALE_HOME_FAILURE', payload: home.Id });
      }
    } catch (error) {
      setTimeout(() => {
        dispatch({ type: 'TOGGLE_SALE_HOME_FAILURE' });
      }, 600);
    }
  };

调度后的更新不是立即可用的,并且是异步的。因此,您的应用将通过另一个渲染周期来反映更新。

您需要使用 useEffect 在更新后调用 api 而不是在初始渲染时调用它。

const initialRender = useRef(true);
useEffect(() => {
    if(initialRender.current) {
         initialRender.current = false;
    } else {
      try {
          const didUpdate = await updateInventory(
            activeLotNumber,
            home.InventoryId,
            {
              InventoryId: home.InventoryId,
              IsSaleHome: !home.IsSaleHome,
            }
          );
          if (didUpdate == true) {
            dispatch({ type: 'TOGGLE_SALE_HOME_SUCCESS' });
          } 
          else {
              dispatch({ type: 'TOGGLE_SALE_HOME_FAILURE', payload: home.Id });
          }
        } catch (error) {
          setTimeout(() => {
            dispatch({ type: 'TOGGLE_SALE_HOME_FAILURE' });
          }, 600);
        }
    }

}, [home])

const toggleSaleHome = async (home, undo = true) => {
    dispatch({ type: 'TOGGLE_SALE_HOME_INIT', payload: home.Id });
}

我最终通过在 'INIT' 之前添加一个 if 语句解决了我的问题。

const toggleSaleHome = async (home, undo = true) => {
    if (saleHomes.length > 9 && !home.IsSaleHome) {
      toast.error(
        <div>
          {`${home.Name} could not be added. You already have selected 10 sale homes.`} 
        </div>,
        {
          className: 'error-toast',
          progressClassName: 'error-progress-bar',
          closeButton: false,
          position: toast.POSITION.BOTTOM_RIGHT,
        }
      );
      return;
    }
    dispatch({ type: 'TOGGLE_SALE_HOME_INIT', payload: home.Id });
    try {
      const didUpdate = await updateInventory(
        activeLotNumber,
        home.InventoryId,
        {
          InventoryId: home.InventoryId,
          IsSaleHome: !home.IsSaleHome,
        }
      );
      if (didUpdate == true) {
        dispatch({ type: 'TOGGLE_SALE_HOME_SUCCESS' });
      } 
      else {
          dispatch({ type: 'TOGGLE_SALE_HOME_FAILURE', payload: home.Id });
      }
    } catch (error) {
      setTimeout(() => {
        dispatch({ type: 'TOGGLE_SALE_HOME_FAILURE' });
      }, 600);
    }
  };

我的整个问题是,我不希望在房屋达到 10 后并且在 'INIT' 实际 saleHomes 状态可用之前切换更多房屋,因此 saleHomes.length是准确的。