perf: optimize load of pagination items with caching
This commit is contained in:
@ -9,27 +9,36 @@ import { fetchRefreshToken } from './authenticationFromServerSide'
|
||||
export class Authentication {
|
||||
public tokens: Tokens
|
||||
public accessTokenAge: number
|
||||
public socket: Socket
|
||||
public socket?: Socket
|
||||
public api: AxiosInstance
|
||||
|
||||
constructor(tokens: Tokens) {
|
||||
constructor(tokens: Tokens, disableSocketIO: boolean = false) {
|
||||
this.tokens = tokens
|
||||
this.accessTokenAge = Date.now()
|
||||
this.socket = io(API_URL, {
|
||||
auth: { token: `Bearer ${tokens.accessToken}` }
|
||||
})
|
||||
this.socket.on('connect_error', (error) => {
|
||||
if (error.message.startsWith('Unauthorized')) {
|
||||
fetchRefreshToken(this.tokens.refreshToken)
|
||||
.then(({ accessToken }) => {
|
||||
this.setAccessToken(accessToken)
|
||||
})
|
||||
.catch(async () => {
|
||||
this.signout()
|
||||
return await Promise.reject(error)
|
||||
})
|
||||
}
|
||||
})
|
||||
if (disableSocketIO || typeof window === 'undefined') {
|
||||
this.socket = undefined
|
||||
} else {
|
||||
this.socket = io(API_URL, {
|
||||
auth: { token: `Bearer ${tokens.accessToken}` }
|
||||
})
|
||||
this.socket.on('connect', () => {
|
||||
console.log(
|
||||
`Connected to socket with clientId: ${this.socket?.id ?? 'undefined'}`
|
||||
)
|
||||
})
|
||||
this.socket.on('connect_error', (error) => {
|
||||
if (error.message.startsWith('Unauthorized')) {
|
||||
fetchRefreshToken(this.tokens.refreshToken)
|
||||
.then(({ accessToken }) => {
|
||||
this.setAccessToken(accessToken)
|
||||
})
|
||||
.catch(async () => {
|
||||
this.signout()
|
||||
return await Promise.reject(error)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
this.api = axios.create({
|
||||
baseURL: API_URL,
|
||||
headers: {
|
||||
@ -83,13 +92,14 @@ export class Authentication {
|
||||
this.tokens.accessToken = accessToken
|
||||
this.accessTokenAge = Date.now()
|
||||
const token = `${this.tokens.type} ${this.tokens.accessToken}`
|
||||
if (typeof this.socket.auth !== 'function') {
|
||||
if (typeof this?.socket?.auth !== 'function' && this.socket != null) {
|
||||
this.socket.auth.token = token
|
||||
}
|
||||
}
|
||||
|
||||
public signout(): void {
|
||||
cookies.remove('refreshToken')
|
||||
window.localStorage.clear()
|
||||
window.location.href = '/authentication/signin'
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,8 @@ export const AuthenticationProvider: React.FC<
|
||||
const [user, setUser] = useState<UserCurrent>(props.authentication.user)
|
||||
|
||||
const authentication = useMemo(() => {
|
||||
return new Authentication(props.authentication.tokens)
|
||||
const disableSocketIO = typeof window === 'undefined'
|
||||
return new Authentication(props.authentication.tokens, disableSocketIO)
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- We only want to run this memo once
|
||||
}, [])
|
||||
@ -33,7 +34,9 @@ export const AuthenticationProvider: React.FC<
|
||||
useEffect(() => {
|
||||
setLanguage(props.authentication.user.settings.language).catch(() => {})
|
||||
setTheme(props.authentication.user.settings.theme)
|
||||
|
||||
return () => {
|
||||
authentication?.socket?.disconnect()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- We only want to run this effect once
|
||||
}, [])
|
||||
|
||||
|
@ -71,7 +71,7 @@ export const authenticationFromServerSide = (
|
||||
} else {
|
||||
try {
|
||||
let data: any = {}
|
||||
const authentication = new Authentication(tokens)
|
||||
const authentication = new Authentication(tokens, true)
|
||||
const { data: currentUser } = await authentication.api.get<
|
||||
unknown,
|
||||
AxiosResponse<UserCurrent>
|
||||
|
34
tools/cache.ts
Normal file
34
tools/cache.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { PaginationItem } from '../hooks/usePagination'
|
||||
|
||||
export const GUILDS_CACHE_KEY = 'guilds' as const
|
||||
export const CHANNELS_CACHE_KEY = 'channels' as const
|
||||
export const MEMBERS_CACHE_KEY = 'members' as const
|
||||
export const MESSAGES_CACHE_KEY = 'messages' as const
|
||||
|
||||
export type CacheKey =
|
||||
| typeof GUILDS_CACHE_KEY
|
||||
| `${number}-${typeof CHANNELS_CACHE_KEY}`
|
||||
| `${number}-${typeof MEMBERS_CACHE_KEY}`
|
||||
| `${number}-${typeof MESSAGES_CACHE_KEY}`
|
||||
|
||||
export const getPaginationCache = <T extends PaginationItem>(
|
||||
key: CacheKey
|
||||
): T[] => {
|
||||
const cache = localStorage.getItem(key)
|
||||
if (cache != null) {
|
||||
try {
|
||||
const data = JSON.parse(cache)
|
||||
if (Array.isArray(data)) {
|
||||
return data
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export const savePaginationCache = <T extends PaginationItem>(
|
||||
key: CacheKey,
|
||||
data: T[]
|
||||
): void => {
|
||||
localStorage.setItem(key, JSON.stringify(data))
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { SetItems } from '../hooks/usePagination'
|
||||
import { CacheKey, savePaginationCache } from './cache'
|
||||
|
||||
export interface Item {
|
||||
id: number
|
||||
@ -13,6 +14,7 @@ export interface SocketData<T extends Item = Item> {
|
||||
export interface HandleSocketDataOptions<T extends Item = Item> {
|
||||
setItems: SetItems<T>
|
||||
data: SocketData<T>
|
||||
cacheKey?: CacheKey
|
||||
}
|
||||
|
||||
export type SocketListener = (data: SocketData) => void
|
||||
@ -20,7 +22,8 @@ export type SocketListener = (data: SocketData) => void
|
||||
export const handleSocketData = <T extends Item = Item>(
|
||||
options: HandleSocketDataOptions<T>
|
||||
): void => {
|
||||
const { data, setItems } = options
|
||||
const { data, setItems, cacheKey } = options
|
||||
console.log('socket.io data received: ', data)
|
||||
|
||||
setItems((oldItems) => {
|
||||
const newItems = [...oldItems]
|
||||
@ -47,6 +50,9 @@ export const handleSocketData = <T extends Item = Item>(
|
||||
break
|
||||
}
|
||||
}
|
||||
if (cacheKey != null) {
|
||||
savePaginationCache(cacheKey, newItems)
|
||||
}
|
||||
return newItems
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user