首页 > 解决方案 > React:查询,延迟使用Reducer

问题描述

我已经在这个问题上摸索了很长一段时间,但我仍然不知道该怎么做。

基本上,我通过 useQuery 从数据库中提取数据,这一切都很好而且花花公子,但我也在尝试使用useReducer(我仍然不是 100% 熟悉)来保存,initial data以便state检测是否已进行任何更改。

问题:

当 useQuery 忙于获取数据时,initial dataundefined; 这就是被保存为state. 这会导致有关验证和保存等方面的各种问题。

这是我的主要表单功能:

function UserAccountDataForm({userId}) {
    const { query: {data: userData, isLoading: isLoadingUserData} } = useUserData(userId);
    
    const rows = React.useMemo(() => {
        if (userData) { /* Process userData here into arrays */ }
        return [];
    }, [isLoadingUserData, userData]); // Watches for changes in these values

    const { isDirty, methods } = useUserDataForm(handleSubmit, userData);
    const { submit, /* updateFunctions here */ } = methods;

    if (isLoadingUserData) { return <AccordionSkeleton /> } // Tried putting this above useUserDataForm, causes issues

    return (
        <>
            Render stuff here
            *isDirty* is used to detect if changes have been made, and enables "Update Button"
        </>
    )
}

这是 useUserData (负责从数据库中提取数据):

export function useUserData(user_id, column = "data") {
    const query = useQuery({
        queryKey: ["user_data", user_id],
        queryFn: () => getUserData(user_id, column), // calls async function for getting stuff from DB
        staleTime: Infinity,
    });
}

return { query }

这是减速器:

function userDataFormReducer(state, action) {
    switch(action.type) {
        case "currency":
            return {... state, currency: action.currency} 
// returns data in the same format as initial data, with updated currency. Of course if state is undefined, formatting all goes to heck
        default:
            return;
    }
}

function useUserDataForm(handleSubmit, userData) {
    const [state, dispatch] = React.useReducer(userDataFormReducer, userData);
    console.log(state) // Sometimes this returns the data as passed; most of the times, it's undefined.
    const isDirty = JSON.stringify(userData) !== JSON.stringify(state); // Which means this is almost always true.

    const updateFunction = (type, value) => { // sample only
        dispatch({type: type, value: value});
    }
}

export { useUserDataForm };

使问题更加复杂的是,它并不总是会发生。主窗体位于<Tab>; 如果用户切换进出选项卡,有时state会在其中包含正确initial data的内容,并且一切都按预期工作。

我能想到的最快的“修复”是initial data在 useQuery 运行时不设置(通过不调用减速器)。不幸的是,我不确定这是否可行。还有什么我可以尝试解决的吗?

标签: reactjsreact-hooksreact-query

解决方案


使问题更加复杂的是,它并不总是会发生。主窗体位于 ; 如果用户切换到选项卡,有时状态会在其中包含正确的初始数据,并且一切都按预期工作。

这很可能是意料之中的,因为useQuery如果它有缓存,它会从缓存中返回数据。因此,如果您回到您的选项卡,useQuery将已经有数据并且只进行后台重新获取。由于useReducer是在组件挂载时发起的,所以在这些场景下可以获取到服务器数据。

有两种方法可以解决此问题:

  1. 拆分执行查询的组件和具有本地状态的组件 (useReducer)。然后,您可以决定仅在查询已经有数据后挂载具有 useReducer 的组件。请注意,如果您这样做,您基本上会选择退出react-query 的所有很酷的后台更新功能:任何可能产生新数据的额外提取都不会被“复制”。这就是为什么我建议如果你这样做,你关闭查询以避免不必要的提取。简单的例子:
const App = () => {
 const { data } = useQuery(key, fn)

 if (!data) return 'Loading...'
 
 return <Form data={data} />

}

const Form = ({ data }) => {
  const [state, dispatch] = useReducer(userDataFormReducer, data)

}

因为只有在数据可用时才会安装减速器,所以你不会遇到这个问题。

  1. 不要在任何地方复制服务器状态:) 我更喜欢这种方法,因为它使服务器和客户端状态分开,并且与 useReducer 配合得很好。这是我的博客中有关如何实现该目标的示例:
const reducer = (amount) => (state, action) => {
  switch (action) {
    case 'increment':
      return state + amount
    case 'decrement':
      return state - amount
  }
}

const useCounterState = () => {
  const { data } = useQuery(['amount'], fetchAmount)
  return React.useReducer(reducer(data ?? 1), 0)
}

function App() {
  const [count, dispatch] = useCounterState()

  return (
    <div>
      Count: {count}
      <button onClick={() => dispatch('increment')}>Increment</button>
      <button onClick={() => dispatch('decrement')}>Decrement</button>
    </div>
  )
}

如果这完全取决于你的减速器试图实现的目标,但它可能看起来像这样:

const App = () => {
 const { data } = useQuery(key, fn)
 const [state, dispatch] = useReducer(userDataFormReducer)
 const currency = state.currency ?? data.currency
}

通过分别保持服务器状态和客户端状态,您将只存储用户选择的内容。像货币这样的“默认值”不在状态中,因为它本质上是状态重复。如果货币未定义,您仍然可以选择显示服务器状态,这要感谢??运营商。

另一个优点是脏检查相对容易(我的客户端状态是否未定义?)并且重置为初始状态也只是意味着将客户端状态设置回未定义。

因此,实际状态本质上是根据您从服务器获得的信息和用户输入的计算状态,当然优先于用户输入。


推荐阅读