如何解决为什么即使以下 HOC 返回了一个已记忆的组件,它也会不断刷新?
对于上下文,我正在将 Django Rest Framework 后端与 Next.js + Next-Auth 集成。除了一部分,我已经完成了大部分整合。要求是拥有一个刷新令牌系统,该系统将在访问令牌即将过期时尝试刷新它。这是我的逻辑:
/api/auth/[...nextauth].ts
import { NextApiRequest,NextApiResponse } from "next";
import NextAuth from "next-auth";
import { NextAuthOptions } from "next-auth";
import Providers from "next-auth/providers";
import axios from "axios";
import { AuthenticatedUser } from "../../../types";
import { JwtUtils,UrlUtils } from "../../../constants/Utils";
namespace NextAuthUtils {
export const refreshToken = async function (refreshToken) {
try {
const response = await axios.post(
// "http://localhost:8000/api/auth/token/refresh/",UrlUtils.makeUrl(
process.env.BACKEND_API_BASE,"auth","token","refresh",),{
refresh: refreshToken,},);
const { access,refresh } = response.data;
// still within this block,return true
return [access,refresh];
} catch {
return [null,null];
}
};
}
const settings: NextAuthOptions = {
secret: process.env.SESSION_SECRET,session: {
jwt: true,maxAge: 24 * 60 * 60,// 24 hours
},jwt: {
secret: process.env.JWT_SECRET,providers: [
Providers.Google({
clientId: process.env.GOOGLE_CLIENT_ID,clientSecret: process.env.GOOGLE_CLIENT_SECRET,}),],callbacks: {
async signIn(user: AuthenticatedUser,account,profile) {
// may have to switch it up a bit for other providers
if (account.provider === "google") {
// extract these two tokens
const { accessToken,idToken } = account;
// make a POST request to the DRF backend
try {
const response = await axios.post(
// tip: use a seperate .ts file or json file to store such URL endpoints
// "http://127.0.0.1:8000/api/social/login/google/",UrlUtils.makeUrl(
process.env.BACKEND_API_BASE,"social","login",account.provider,{
access_token: accessToken,// note the differences in key and value variable names
id_token: idToken,);
// extract the returned token from the DRF backend and add it to the `user` object
const { access_token,refresh_token } = response.data;
user.accessToken = access_token;
user.refreshToken = refresh_token;
return true; // return true if everything went well
} catch (error) {
return false;
}
}
return false;
},async jwt(token,user: AuthenticatedUser,profile,isNewUser) {
if (user) {
const { accessToken,refreshToken } = user;
// reform the `token` object from the access token we appended to the `user` object
token = {
...token,accessToken,refreshToken,};
// remove the tokens from the user objects just so that we don't leak it somehow
delete user.accessToken;
delete user.refreshToken;
return token;
}
// token has been invalidated,try refreshing it
if (JwtUtils.isJwtExpired(token.accessToken as string)) {
const [
newAccessToken,newRefreshToken,] = await NextAuthUtils.refreshToken(token.refreshToken);
if (newAccessToken && newRefreshToken) {
token = {
...token,accessToken: newAccessToken,refreshToken: newRefreshToken,iat: Math.floor(Date.now() / 1000),exp: Math.floor(Date.now() / 1000 + 2 * 60 * 60),};
return token;
}
// unable to refresh tokens from DRF backend,invalidate the token
return {
...token,exp: 0,};
}
// token valid
return token;
},async session(session,userOrToken) {
session.accessToken = userOrToken.accessToken;
return session;
},};
export default (req: NextApiRequest,res: NextApiResponse) =>
NextAuth(req,res,settings);
接下来,the example in the Next-Auth documentation 展示了 useSession()
钩子的使用。但我不喜欢它,因为:
- 除非刷新窗口本身(它是一个 open issue),否则一旦访问令牌刷新,它就不会更新会话的状态
- 感觉就像每个想要使用会话的组件都有很多代码重复,还有检查会话对象是否存在、会话是否正在加载等的守卫。所以我想使用 HOC。
因此,我想出了以下解决方案:
constants/Hooks.tsx
import { Session } from "next-auth";
import { useEffect,useMemo,useState } from "react";
export function useAuth(refreshInterval?: number): [Session,boolean] {
/*
custom hook that keeps the session up-to-date by refreshing it
@param {number} refreshInterval: The refresh/polling interval in seconds. default is 10.
@return {tuple} A tuple of the Session and boolean
*/
const [session,setSession] = useState<Session>(null);
const [loading,setLoading] = useState<boolean>(false);
useEffect(() => {
async function fetchSession() {
let sessionData: Session = null;
setLoading(true);
const response = await fetch("/api/auth/session");
if (response.ok) {
const data: Session = await response.json();
if (Object.keys(data).length > 0) {
sessionData = data;
}
}
setSession(sessionData);
setLoading(false);
}
refreshInterval = refreshInterval || 10;
fetchSession();
const interval = setInterval(() => fetchSession(),refreshInterval * 1000);
return () => clearInterval(interval);
},[]);
return [session,loading];
}
constants/HOCs.tsx
import { Session } from "next-auth";
import { signIn } from "next-auth/client";
import React from "react";
import { useAuth } from "./Hooks";
type TSessionProps = {
session: Session;
};
export function withAuth<P extends object>(Component: React.ComponentType<P>) {
return React.memo(function (props: Exclude<P,TSessionProps>) {
const [session,loading] = useAuth(); // custom hook call
if (loading) {
return <h2>Loading...</h2>;
}
if (!loading && !session) {
return (
<>
Not signed in <br />
<button onClick={() => signIn()}>Sign in</button>
<pre>{!session && "User is not logged in"}</pre>
</>
);
}
return <Component {...props} session={session} />;
});
}
然后,在我有定期数据获取要求的组件中(我知道这可以以更好的方式实现,这只是一个人为的示例,我试图模拟用户不活动但应用程序仍然可以在如果需要背景),我正在使用 HOC:
pages/posts.tsx
import React,{ useEffect,useState } from "react";
import Post from "../components/Post";
import { withAuth } from "../constants/HOCs";
import { TPost } from "../constants/Types";
import Link from "next/link";
function Posts(props) {
const { session } = props;
// const [session,loading] = useAuth();
const [posts,setPosts] = useState<TPost[]>([]);
const [fetchingPosts,setFetchingPosts] = useState<boolean>(false);
useEffect(() => {
if (!session) {
return;
}
async function getPosts() {
setFetchingPosts(true);
const response = await fetch("http://127.0.0.1:8000/api/posts",{
method: "get",headers: new Headers({
Authorization: `Bearer ${session?.accessToken}`,});
if (response.ok) {
const posts: TPost[] = await response.json();
setPosts(posts);
}
setFetchingPosts(false);
}
// initiate the post fetching mechanism once
getPosts();
const intervalId = setInterval(() => getPosts(),10 * 1000);
// useEffect cleanup
return () => clearInterval(intervalId);
},[JSON.stringify(session)]);
// {
// loading && <h2>Loading...</h2>;
// }
// {
// !loading && !session && (
// <>
// Not signed in <br />
// <button onClick={() => signIn()}>Sign in</button>
// <pre>{!session && "User is not logged in"}</pre>
// </>
// );
// }
return (
<div>
<h2>Fetched at {JSON.stringify(new Date())}</h2>
<Link href="/">Back to homepage</Link>
{posts.map((post) => (
<Post key={post.title} post={post} />
))}
</div>
);
}
export default withAuth(Posts);
问题在于,由于 withAuth
HOC 以及可能由于每 10 秒的 useAuth
钩子,整个页面都会重新呈现。但是,我没有运气尝试调试它。也许我在我的 React 概念中遗漏了一些关键的东西。我感谢任何和所有可能的建议/帮助。提前致谢。
附注。我知道一个使用 SWR 库的解决方案,但我想尽可能避免使用该库。
解决方法
在花费大量时间尝试解决此问题后,我最终使用了 useSwr()
钩子。最后还为有兴趣的人写了this article。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。