如何编写依赖于 React 查询调用的自定义挂钩
How to compose custom hooks that depend on react query calls
我有几个钩子可以使用 React 查询获取数据并使用该数据进行低级计算。另一个自定义挂钩将使用该数据的输出来计算不同的值。
例如:
- 挂钩 #1 获取用户租金的未来支出列表 属性(例如熔炉、屋顶等)。
- Hook #2 获取用户的偏好,应该考虑和节省未来多长时间的费用(例如,只有未来 5 年内到期的费用是相关的)。
- 挂钩 #3 调用挂钩 #1 和 #2 以及 returns 每个月应留出的总金额以支付未来的费用。
由于 hook #3 依赖于前两个 hook 的数据,我不确定如何构建该代码。 React 查询 returns 数据和加载状态,因此我目前正在合并来自挂钩 #1 和 #2 的加载状态,并将来自挂钩 #3 的数据计算置于条件中。但这会导致重新渲染循环错误。
知道为什么会这样吗?
const usePropertyTotalMonthlyCapex = (propertyId) => {
const [preferences, preferencesIsLoading, preferencesIsError, preferencesError] = useGetUserPreferences()
const [expenses, isLoading, isError, error] = useGetExpenses(propertyId)
const [capex, setCapex] = useState(0)
if (preferences && expenses) { // this seems to be causing a render loop error
if (expenses.length === 0) {
setCapex(0)
} else {
const { outlookLength } = preferences
const expenseValues = expenses?.map(expense => {
const { lifespan, age, replacementCost } = expense
if (lifespan - age > outlookLength) {
return 0
}
return replacementCost / ((lifespan - age) * 12)
})
setCapex(expenseValues.reduce((acc, init) => acc + init).toFixed(0))
}
}
return [capex, preferencesIsLoading && isLoading]
函数组件,钩子定义,需要是无副作用的纯函数;他们不能直接设置状态。相反,您需要将代码的计算逻辑包装在 useEffect
或 useLayoutEffect
中(取决于您是希望它在渲染之后还是之前触发);参见 effect hook。例如:
const usePropertyTotalMonthlyCapex = (propertyId) => {
const [preferences, preferencesIsLoading, preferencesIsError, preferencesError] = useGetUserPreferences()
const [expenses, isLoading, isError, error] = useGetExpenses(propertyId)
const [capex, setCapex] = useState(0)
useLayoutEffect(() => {
if (preferences && expenses) {
if (expenses.length === 0) {
setCapex(0)
} else {
const { outlookLength } = preferences
const expenseValues = expenses?.map(expense => {
const { lifespan, age, replacementCost } = expense
if (lifespan - age > outlookLength) {
return 0
}
return replacementCost / ((lifespan - age) * 12)
})
setCapex(expenseValues.reduce((acc, init) => acc + init).toFixed(0))
}
}
}, [preferences, expenses])
return [capex, preferencesIsLoading && isLoading]
}
你不需要状态。 capex
是 派生状态 可以根据您已有的状态计算得出。将派生状态放入状态可能是一种反模式。如果 useState
的 setter 仅在 effect 中被调用,您可以发现它。由于计算 capex
是纯粹的,您可以在渲染期间调用它:
const computeCapex = (preferences, expenses) => {
if (preferences && expenses) {
if (expenses.length === 0) {
return 0
} else {
const { outlookLength } = preferences
const expenseValues = expenses?.map(expense => {
const { lifespan, age, replacementCost } = expense
if (lifespan - age > outlookLength) {
return 0
}
return replacementCost / ((lifespan - age) * 12)
})
return expenseValues.reduce((acc, init) => acc + init).toFixed(0))
}
}
}
const usePropertyTotalMonthlyCapex = (propertyId) => {
const [preferences, preferencesIsLoading, preferencesIsError, preferencesError] = useGetUserPreferences()
const [expenses, isLoading, isError, error] = useGetExpenses(propertyId)
const capex = computeCapex(preferences, expenses)
return [capex, preferencesIsLoading && isLoading]
}
如果计算量很大,您可以将调用包装在 useMemo
中,这是为此而设计的:
const usePropertyTotalMonthlyCapex = (propertyId) => {
const [preferences, preferencesIsLoading, preferencesIsError, preferencesError] = useGetUserPreferences()
const [expenses, isLoading, isError, error] = useGetExpenses(propertyId)
const capex = React.useMemo(
() => computeCapex(preferences, expenses),
[preferences, expenses]
)
return [capex, preferencesIsLoading && isLoading]
}
我有几个钩子可以使用 React 查询获取数据并使用该数据进行低级计算。另一个自定义挂钩将使用该数据的输出来计算不同的值。
例如:
- 挂钩 #1 获取用户租金的未来支出列表 属性(例如熔炉、屋顶等)。
- Hook #2 获取用户的偏好,应该考虑和节省未来多长时间的费用(例如,只有未来 5 年内到期的费用是相关的)。
- 挂钩 #3 调用挂钩 #1 和 #2 以及 returns 每个月应留出的总金额以支付未来的费用。
由于 hook #3 依赖于前两个 hook 的数据,我不确定如何构建该代码。 React 查询 returns 数据和加载状态,因此我目前正在合并来自挂钩 #1 和 #2 的加载状态,并将来自挂钩 #3 的数据计算置于条件中。但这会导致重新渲染循环错误。
知道为什么会这样吗?
const usePropertyTotalMonthlyCapex = (propertyId) => {
const [preferences, preferencesIsLoading, preferencesIsError, preferencesError] = useGetUserPreferences()
const [expenses, isLoading, isError, error] = useGetExpenses(propertyId)
const [capex, setCapex] = useState(0)
if (preferences && expenses) { // this seems to be causing a render loop error
if (expenses.length === 0) {
setCapex(0)
} else {
const { outlookLength } = preferences
const expenseValues = expenses?.map(expense => {
const { lifespan, age, replacementCost } = expense
if (lifespan - age > outlookLength) {
return 0
}
return replacementCost / ((lifespan - age) * 12)
})
setCapex(expenseValues.reduce((acc, init) => acc + init).toFixed(0))
}
}
return [capex, preferencesIsLoading && isLoading]
函数组件,钩子定义,需要是无副作用的纯函数;他们不能直接设置状态。相反,您需要将代码的计算逻辑包装在 useEffect
或 useLayoutEffect
中(取决于您是希望它在渲染之后还是之前触发);参见 effect hook。例如:
const usePropertyTotalMonthlyCapex = (propertyId) => {
const [preferences, preferencesIsLoading, preferencesIsError, preferencesError] = useGetUserPreferences()
const [expenses, isLoading, isError, error] = useGetExpenses(propertyId)
const [capex, setCapex] = useState(0)
useLayoutEffect(() => {
if (preferences && expenses) {
if (expenses.length === 0) {
setCapex(0)
} else {
const { outlookLength } = preferences
const expenseValues = expenses?.map(expense => {
const { lifespan, age, replacementCost } = expense
if (lifespan - age > outlookLength) {
return 0
}
return replacementCost / ((lifespan - age) * 12)
})
setCapex(expenseValues.reduce((acc, init) => acc + init).toFixed(0))
}
}
}, [preferences, expenses])
return [capex, preferencesIsLoading && isLoading]
}
你不需要状态。 capex
是 派生状态 可以根据您已有的状态计算得出。将派生状态放入状态可能是一种反模式。如果 useState
的 setter 仅在 effect 中被调用,您可以发现它。由于计算 capex
是纯粹的,您可以在渲染期间调用它:
const computeCapex = (preferences, expenses) => {
if (preferences && expenses) {
if (expenses.length === 0) {
return 0
} else {
const { outlookLength } = preferences
const expenseValues = expenses?.map(expense => {
const { lifespan, age, replacementCost } = expense
if (lifespan - age > outlookLength) {
return 0
}
return replacementCost / ((lifespan - age) * 12)
})
return expenseValues.reduce((acc, init) => acc + init).toFixed(0))
}
}
}
const usePropertyTotalMonthlyCapex = (propertyId) => {
const [preferences, preferencesIsLoading, preferencesIsError, preferencesError] = useGetUserPreferences()
const [expenses, isLoading, isError, error] = useGetExpenses(propertyId)
const capex = computeCapex(preferences, expenses)
return [capex, preferencesIsLoading && isLoading]
}
如果计算量很大,您可以将调用包装在 useMemo
中,这是为此而设计的:
const usePropertyTotalMonthlyCapex = (propertyId) => {
const [preferences, preferencesIsLoading, preferencesIsError, preferencesError] = useGetUserPreferences()
const [expenses, isLoading, isError, error] = useGetExpenses(propertyId)
const capex = React.useMemo(
() => computeCapex(preferences, expenses),
[preferences, expenses]
)
return [capex, preferencesIsLoading && isLoading]
}