RTK 查询 queryFulfilled 似乎返回旧数据。 resetApiState() 和 reset() 不起作用

问题描述 投票:0回答:0

我正在尝试使用 MERN 堆栈和 RTK 查询构建一个 Note 项目,访问令牌存储在内存中,刷新令牌存储在 http-only cookie 中(不在 localStorage 中存储令牌或用户数据),所以我使用 useEffect()如果用户刷新或关闭 - 重新打开页面,则通过发送刷新令牌来保持登录()。

我还实现了一项功能,一旦登录,用户名就会显示在

上。

这是我面临的问题: 我从 http://localhost:3000/ () 开始,我通过在 Chrome 中输入 URL 转到了 http://localhost:3000/login ()。在我登录后(将我导航到 ),如果我刷新页面(我相信这激活了 中的 useEffect()),然后注销(在 Redux DevTool 中,存储和订阅中的令牌已被清除),现在我在,我不再看到

上的用户名,但是现在当我返回几页,再次到时,用户名再次出现,authApiSlice.js中
console.log(data)
中的
refresh
显示在那里是token,也就是我退出前刷新页面时出现的那个token。

如果我不刷新页面,一切正常,我在注销后返回的任何页面都看不到用户名和令牌。

如果我在注销后手动清除 Chrome 设置中的“缓存图像和文件”,就在我返回之前,一切正常。

难道是当我注销后返回时,authApiSlice.js 中

queryFulfilled
中的
refresh
以某种方式将旧的访问令牌返回给我?

完整代码:https://github.com/ciaooociaooociaoooo/test

App.js:

<Route element={<ErrorBoundary />}>
        <Route path='/' element={<PersistLayout />}>
          {/* Public */}
          <Route index element={<Public />} />
          <Route path='login' element={<Login />} />

          {/* Protected Routes */}
          <Route element={<Protect />}>
            <Route
              element={<RequireAuth allowedRoles={[...Object.values(ROLES)]} />}
            >
              <Route element={<Prefetch />}>
                <Route path='dash' element={<DashLayout />}>
                  <Route index element={<Welcome />} />

                  <Route element={<RequireAuth allowedRoles={[ROLES.Admin]} />}>
                    <Route path='users'>
                      <Route index element={<UsersList />} />
                      <Route path=':id' element={<EditUser />} />
                      <Route path='new' element={<NewUserForm />} />
                    </Route>
                  </Route>

{/* ... */}

authApiSlice.js:

export const authApiSlice = apiSlice.injectEndpoints({
  endpoints: (builder) => ({
    login: builder.mutation({
      query: (credentials) => ({
        url: '/auth',
        method: 'POST',
        body: { ...credentials },
      }),
      async onQueryStarted(arg, { dispatch, queryFulfilled }) {
        try {
          const {
            data: { accessToken },
          } = await queryFulfilled
          dispatch(setCredentials({ accessToken }))
        } catch (err) {
          console.log(err)
        }
      },
    }),
    sendLogout: builder.mutation({
      query: () => ({
        url: '/auth/logout',
        method: 'POST',
      }),
      async onQueryStarted(arg, { dispatch, queryFulfilled }) {
        try {
          const { data } = await queryFulfilled
          console.log(data)
          dispatch(logout())
          setTimeout(() => {
            dispatch(apiSlice.util.resetApiState())
          }, 1000)
        } catch (err) {
          console.log(err)
        }
      },
    }),
    refresh: builder.mutation({
      query: () => ({
        url: '/auth/refresh',
        method: 'GET',
      }),
      async onQueryStarted(arg, { dispatch, queryFulfilled }) {
        try {
          const { data } = await queryFulfilled
          console.log(data)
          const { accessToken } = data
          dispatch(setCredentials({ accessToken }))
        } catch (err) {
          console.log(err)
        }
      },
    }),
  }),
})

export const { useLoginMutation, useSendLogoutMutation, useRefreshMutation } =
  authApiSlice

authSlice.js:

const authSlice = createSlice({
  name: 'auth',
  initialState: { token: null },
  reducers: {
    setCredentials: (state, action) => {
      const { accessToken } = action.payload
      console.log('setCredentials: ', accessToken)
      state.token = accessToken
    },
    logout: (state, action) => {
      state.token = null
    },
  },
})

export const { setCredentials, logout } = authSlice.actions

export default authSlice

export const selectCurrentToken = (state) => state.auth.token

PersistLayout.js:

const PersistLayout = () => {
  const effectRan = useRef(false)
  const token = useSelector(selectCurrentToken)
  const [refresh, { isLoading, isError }] = useRefreshMutation()

  useEffect(() => {
    if (effectRan.current === true || process.env.NODE_ENV !== 'development') {
      const verifyRefreshToken = async () => {
        console.log('persist: sending refresh token')
        try {
          await refresh()
        } catch (err) {
          console.log(err)
        }
      }

      if (!token) verifyRefreshToken()
    }
    return () => {
      effectRan.current = true
    }
    // eslint-disable-next-line
  }, [token])

  let content
  if (isLoading) {
    console.log('loading')
    content = <PulseLoader color={'#FFF'} />
  } else if (isError) {
    console.log('Persist: isError')
    content = <Outlet />
  } else {
    content = <Outlet />
  }
  return (
    <>
      <Header />
      <div>{content}</div>
    </>
  )
}

Header.js:

const Header = () => {
  const { username, token } = useAuth()
  const [sendLogout, { isSuccess }] = useSendLogoutMutation()
  const navigate = useNavigate()

  useEffect(() => {
    if (isSuccess) navigate('/')
  }, [isSuccess, navigate])

  const onNewUserClicked = () => navigate('/dash/users/new')
  const onUsersClicked = () => navigate('/dash/users')

  let btnLogout
  let btnNewUser
  let btnUsersList
  if (token) {
    btnLogout = (
      <button title='Logout' onClick={sendLogout}>
        Logout
      </button>
    )
    btnNewUser = (
      <button title='New User' onClick={onNewUserClicked}>
        New User
      </button>
    )
    btnUsersList = (
      <button title='Users' onClick={onUsersClicked}>
        Users List
      </button>
    )
  }

  return (
    <header>
      <p>{username}</p>
      {btnLogout}
      {btnNewUser}
      {btnUsersList}
    </header>
  )
}

公共.js:

const Public = () => {
  return <div>Public</div>
}

登录.js:

const Login = () => {
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')
  const [errMsg, setErrMsg] = useState('')
  const userRef = useRef()
  const errRef = useRef()
  const navigate = useNavigate()

  const [login, { isSuccess, isLoading, isError, error }] = useLoginMutation()

  useEffect(() => {
    userRef.current.focus()
  }, [])

  useEffect(() => {
    if (isSuccess) {
      setUsername('')
      setPassword('')
      navigate('/dash')
    }
  }, [isSuccess, navigate])

  useEffect(() => {
    setErrMsg('')
  }, [username, password])

  useEffect(() => {
    if (isError) {
      if (error.status === 'FETCH_ERROR') {
        setErrMsg('No Server Response')
      } else {
        setErrMsg(error.data?.message)
      }
      errRef.current.focus()
    }
  }, [isError, error])

  const onUsernameChanged = (e) => {
    setUsername(e.target.value)
  }

  const onPasswordChanged = (e) => {
    setPassword(e.target.value)
  }

  let canSave = username && password && !isLoading

  const onSubmit = async (e) => {
    e.preventDefault()
    await login({ username, password })
  }

  let content
  content = (
    <>
      <p ref={errRef} aria-live='assertive'>
        {errMsg}
      </p>
      <form onSubmit={onSubmit}>
        <label htmlFor='username'>Username</label>
        <input
          id='username'
          name='username'
          type='text'
          autoComplete='off'
          value={username}
          ref={userRef}
          onChange={onUsernameChanged}
        />

        <label htmlFor='password'>Password</label>
        <input
          id='password'
          name='password'
          type='password'
          autoComplete='off'
          value={password}
          onChange={onPasswordChanged}
        />

        <button type='submit' title='Login' disabled={!canSave}>
          Login
        </button>
      </form>
    </>
  )

  if (isLoading) content = <PuffLoader color='#3449bb' />

  return content
}

Protect.js:

const Protect = () => {
  const token = useSelector(selectCurrentToken)

  console.log('Protect: ', token)

  let content = <Outlet />
  if (!token) content = <p>No token</p>

  return content
}

RequireAuth.js

const RequireAuth = ({ allowedRoles }) => {
  const { roles } = useAuth()

  const content = roles?.some((role) => allowedRoles.includes(role)) ? (
    <Outlet />
  ) : (
    <Unauthorized />
  )

  return content
}

export default RequireAuth

Prefetch.js:

const Prefetch = () => {
  useEffect(() => {
    console.log('subscribing')
    const notes = store.dispatch(notesApiSlice.endpoints.getNotes.initiate())
    const users = store.dispatch(usersApiSlice.endpoints.getUsers.initiate())

    return () => {
      console.log('unsubscribing')
      notes.unsubscribe()
      users.unsubscribe()
    }
  }, [])

  return <Outlet />
}

DashLayout.js

const DashLayout = () => {
  return <Outlet />
}

Welcome.js

const Welcome = () => {
  return <div>Welcome</div>
}

我已经尝试过:

const [refresh, { isLoading, isError, reset }] = useRefreshMutation()
但是没用。

PersistLayout.js:

const PersistLayout = () => {
  const effectRan = useRef(false)
  const token = useSelector(selectCurrentToken)
  const [refresh, { isLoading, isError, reset }] = useRefreshMutation()

  useEffect(() => {
    if (effectRan.current === true || process.env.NODE_ENV !== 'development') {
      const verifyRefreshToken = async () => {
        console.log('persist: sending refresh token')
        try {
          await refresh()
          reset()
        } catch (err) {
          console.log(err)
        }
      }

      if (!token) verifyRefreshToken()
    }
    return () => {
      effectRan.current = true
    }
    // eslint-disable-next-line
  }, [token])

  let content
  if (isLoading) {
    console.log('loading')
    content = <PulseLoader color={'#FFF'} />
  } else if (isError) {
    console.log('Persist: isError')
    content = <Outlet />
  } else {
    content = <Outlet />
  }
  return (
    <>
      <Header />
      <div>{content}</div>
    </>
  )
}
reactjs react-redux redux-toolkit mern rtk-query
© www.soinside.com 2019 - 2024. All rights reserved.