在 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是准确的。
我正在使用 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是准确的。