fix: cache with duplicated data
This commit is contained in:
parent
3d185bf044
commit
a068d31d14
@ -1,3 +1,4 @@
|
||||
import { memo } from 'react'
|
||||
import classNames from 'clsx'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
@ -14,7 +15,7 @@ export interface ChannelProps {
|
||||
selected?: boolean
|
||||
}
|
||||
|
||||
export const Channel: React.FC<ChannelProps> = (props) => {
|
||||
const ChannelMemo: React.FC<ChannelProps> = (props) => {
|
||||
const { channel, path, selected = false } = props
|
||||
const router = useRouter()
|
||||
|
||||
@ -53,3 +54,5 @@ export const Channel: React.FC<ChannelProps> = (props) => {
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export const Channel = memo(ChannelMemo)
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { memo } from 'react'
|
||||
import Image from 'next/image'
|
||||
|
||||
import { GuildWithDefaultChannelId } from '../../../models/Guild'
|
||||
@ -8,7 +9,7 @@ export interface GuildProps {
|
||||
selected?: boolean
|
||||
}
|
||||
|
||||
export const Guild: React.FC<GuildProps> = (props) => {
|
||||
const GuildMemo: React.FC<GuildProps> = (props) => {
|
||||
const { guild, selected } = props
|
||||
|
||||
return (
|
||||
@ -34,3 +35,5 @@ export const Guild: React.FC<GuildProps> = (props) => {
|
||||
</IconLink>
|
||||
)
|
||||
}
|
||||
|
||||
export const Guild = memo(GuildMemo)
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { memo } from 'react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
|
||||
@ -8,7 +9,7 @@ export interface MemberProps {
|
||||
member: MemberWithPublicUser
|
||||
}
|
||||
|
||||
export const Member: React.FC<MemberProps> = (props) => {
|
||||
const MemberMemo: React.FC<MemberProps> = (props) => {
|
||||
const { member } = props
|
||||
|
||||
return (
|
||||
@ -45,3 +46,5 @@ export const Member: React.FC<MemberProps> = (props) => {
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export const Member = memo(MemberMemo)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createContext, useContext, useEffect } from 'react'
|
||||
import { createContext, useContext, useEffect, useMemo } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { NextPage, usePagination } from '../hooks/usePagination'
|
||||
@ -28,7 +28,9 @@ export const ChannelsProvider: React.FC<
|
||||
const router = useRouter()
|
||||
const { authentication } = useAuthentication()
|
||||
|
||||
const cacheKey: CacheKey = `${path.guildId}-${CHANNELS_CACHE_KEY}`
|
||||
const cacheKey = useMemo<CacheKey>(() => {
|
||||
return `${path.guildId}-${CHANNELS_CACHE_KEY}`
|
||||
}, [path.guildId])
|
||||
|
||||
const {
|
||||
items: channels,
|
||||
@ -75,7 +77,7 @@ export const ChannelsProvider: React.FC<
|
||||
export const useChannels = (): Channels => {
|
||||
const channels = useContext(ChannelsContext)
|
||||
if (channels === defaultChannelsContext) {
|
||||
throw new Error('useChannels must be used within ChannelsProvider')
|
||||
throw new Error('`useChannels` must be used within `ChannelsProvider`')
|
||||
}
|
||||
return channels
|
||||
}
|
||||
|
@ -11,9 +11,7 @@ export interface GuildMember {
|
||||
member: Member
|
||||
}
|
||||
|
||||
export interface GuildMemberResult extends GuildMember {
|
||||
setGuildMember: React.Dispatch<React.SetStateAction<GuildMember>>
|
||||
}
|
||||
export interface GuildMemberResult extends GuildMember {}
|
||||
|
||||
export interface GuildMemberProps {
|
||||
guildMember: GuildMember
|
||||
@ -79,8 +77,7 @@ export const GuildMemberProvider: React.FC<
|
||||
return (
|
||||
<GuildMemberContext.Provider
|
||||
value={{
|
||||
...guildMember,
|
||||
setGuildMember
|
||||
...guildMember
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@ -91,7 +88,9 @@ export const GuildMemberProvider: React.FC<
|
||||
export const useGuildMember = (): GuildMemberResult => {
|
||||
const guildMember = useContext(GuildMemberContext)
|
||||
if (guildMember === defaultGuildMemberContext) {
|
||||
throw new Error('useGuildMember must be used within GuildMemberProvider')
|
||||
throw new Error(
|
||||
'`useGuildMember` must be used within `GuildMemberProvider`'
|
||||
)
|
||||
}
|
||||
return guildMember
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ export const GuildsProvider: React.FC<React.PropsWithChildren<{}>> = (
|
||||
export const useGuilds = (): Guilds => {
|
||||
const guilds = useContext(GuildsContext)
|
||||
if (guilds === defaultGuildsContext) {
|
||||
throw new Error('useGuilds must be used within GuildsProvider')
|
||||
throw new Error('`useGuilds` must be used within `GuildsProvider`')
|
||||
}
|
||||
return guilds
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createContext, useContext, useEffect } from 'react'
|
||||
import { createContext, useContext, useEffect, useMemo } from 'react'
|
||||
|
||||
import { NextPage, usePagination } from '../hooks/usePagination'
|
||||
import { useAuthentication } from '../tools/authentication'
|
||||
@ -28,7 +28,9 @@ export const MembersProviders: React.FC<
|
||||
|
||||
const { authentication } = useAuthentication()
|
||||
|
||||
const cacheKey: CacheKey = `${path.guildId}-${MEMBERS_CACHE_KEY}`
|
||||
const cacheKey = useMemo<CacheKey>(() => {
|
||||
return `${path.guildId}-${MEMBERS_CACHE_KEY}`
|
||||
}, [path.guildId])
|
||||
|
||||
const {
|
||||
items: members,
|
||||
@ -89,7 +91,7 @@ export const MembersProviders: React.FC<
|
||||
export const useMembers = (): Members => {
|
||||
const members = useContext(MembersContext)
|
||||
if (members === defaultMembersContext) {
|
||||
throw new Error('useMembers must be used within MembersProvider')
|
||||
throw new Error('`useMembers` must be used within `MembersProvider`')
|
||||
}
|
||||
return members
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createContext, useContext, useEffect } from 'react'
|
||||
import { createContext, useContext, useEffect, useMemo } from 'react'
|
||||
|
||||
import { NextPage, usePagination } from '../hooks/usePagination'
|
||||
import { useAuthentication } from '../tools/authentication'
|
||||
@ -26,7 +26,9 @@ export const MessagesProvider: React.FC<
|
||||
const { path, children } = props
|
||||
const { authentication, user } = useAuthentication()
|
||||
|
||||
const cacheKey: CacheKey = `${path.channelId}-${MESSAGES_CACHE_KEY}`
|
||||
const cacheKey = useMemo<CacheKey>(() => {
|
||||
return `${path.channelId}-${MESSAGES_CACHE_KEY}`
|
||||
}, [path.channelId])
|
||||
|
||||
const {
|
||||
items: messages,
|
||||
@ -88,7 +90,7 @@ export const MessagesProvider: React.FC<
|
||||
export const useMessages = (): Messages => {
|
||||
const messages = useContext(MessagesContext)
|
||||
if (messages === defaultMessagesContext) {
|
||||
throw new Error('useMessages must be used within a MessagesProvider')
|
||||
throw new Error('`useMessages` must be used within a `MessagesProvider`')
|
||||
}
|
||||
return messages
|
||||
}
|
||||
|
@ -241,8 +241,8 @@ describe('Pages > /application/[guildId]/[channelId]', () => {
|
||||
cy.visit('/application/abc/abc', {
|
||||
failOnStatusCode: false
|
||||
})
|
||||
.location('pathname')
|
||||
.should('eq', '/404')
|
||||
.get('[data-cy=status-code]')
|
||||
.contains('404')
|
||||
})
|
||||
|
||||
it("should redirect the user to `/404` if `guildId` doesn't exist", () => {
|
||||
@ -253,8 +253,8 @@ describe('Pages > /application/[guildId]/[channelId]', () => {
|
||||
cy.visit(`/application/123/${channelExample.id}`, {
|
||||
failOnStatusCode: false
|
||||
})
|
||||
.location('pathname')
|
||||
.should('eq', '/404')
|
||||
.get('[data-cy=status-code]')
|
||||
.contains('404')
|
||||
})
|
||||
|
||||
it("should redirect the user to `/404` if `channelId` doesn't exist", () => {
|
||||
@ -263,8 +263,8 @@ describe('Pages > /application/[guildId]/[channelId]', () => {
|
||||
getGuildMemberWithGuildIdHandler
|
||||
]).setCookie('refreshToken', 'refresh-token')
|
||||
cy.visit(`/application/${guildExample.id}/123`, { failOnStatusCode: false })
|
||||
.location('pathname')
|
||||
.should('eq', '/404')
|
||||
.get('[data-cy=status-code]')
|
||||
.contains('404')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -92,8 +92,8 @@ describe('Pages > /application/[guildId]/[channelId]/settings', () => {
|
||||
cy.visit(`/application/${guildExample.id}/${channelExample.id}/settings`, {
|
||||
failOnStatusCode: false
|
||||
})
|
||||
.location('pathname')
|
||||
.should('eq', '/404')
|
||||
.get('[data-cy=status-code]')
|
||||
.contains('404')
|
||||
})
|
||||
|
||||
it('should redirect the user to `/404` if `guildId` or `channelId` are not numbers', () => {
|
||||
@ -104,8 +104,8 @@ describe('Pages > /application/[guildId]/[channelId]/settings', () => {
|
||||
cy.visit('/application/abc/abc/settings', {
|
||||
failOnStatusCode: false
|
||||
})
|
||||
.location('pathname')
|
||||
.should('eq', '/404')
|
||||
.get('[data-cy=status-code]')
|
||||
.contains('404')
|
||||
})
|
||||
|
||||
it("should redirect the user to `/404` if `guildId` doesn't exist", () => {
|
||||
@ -116,8 +116,8 @@ describe('Pages > /application/[guildId]/[channelId]/settings', () => {
|
||||
cy.visit(`/application/123/${channelExample.id}/settings`, {
|
||||
failOnStatusCode: false
|
||||
})
|
||||
.location('pathname')
|
||||
.should('eq', '/404')
|
||||
.get('[data-cy=status-code]')
|
||||
.contains('404')
|
||||
})
|
||||
|
||||
it("should redirect the user to `/404` if `channelId` doesn't exist", () => {
|
||||
@ -128,8 +128,8 @@ describe('Pages > /application/[guildId]/[channelId]/settings', () => {
|
||||
cy.visit(`/application/${guildExample.id}/123/settings`, {
|
||||
failOnStatusCode: false
|
||||
})
|
||||
.location('pathname')
|
||||
.should('eq', '/404')
|
||||
.get('[data-cy=status-code]')
|
||||
.contains('404')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -56,8 +56,8 @@ describe('Pages > /application/[guildId]/channels/create', () => {
|
||||
cy.visit('/application/abc/channels/create', {
|
||||
failOnStatusCode: false
|
||||
})
|
||||
.location('pathname')
|
||||
.should('eq', '/404')
|
||||
.get('[data-cy=status-code]')
|
||||
.contains('404')
|
||||
})
|
||||
|
||||
it("should redirect the user to `/404` if `guildId` doesn't exist", () => {
|
||||
@ -68,8 +68,8 @@ describe('Pages > /application/[guildId]/channels/create', () => {
|
||||
cy.visit(`/application/123/channels/create`, {
|
||||
failOnStatusCode: false
|
||||
})
|
||||
.location('pathname')
|
||||
.should('eq', '/404')
|
||||
.get('[data-cy=status-code]')
|
||||
.contains('404')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -86,8 +86,8 @@ describe('Pages > /application/[guildId]/settings', () => {
|
||||
cy.visit('/application/abc/settings', {
|
||||
failOnStatusCode: false
|
||||
})
|
||||
.location('pathname')
|
||||
.should('eq', '/404')
|
||||
.get('[data-cy=status-code]')
|
||||
.contains('404')
|
||||
})
|
||||
|
||||
it("should redirect the user to `/404` if `guildId` doesn't exist", () => {
|
||||
@ -98,8 +98,8 @@ describe('Pages > /application/[guildId]/settings', () => {
|
||||
cy.visit(`/application/123/settings`, {
|
||||
failOnStatusCode: false
|
||||
})
|
||||
.location('pathname')
|
||||
.should('eq', '/404')
|
||||
.get('[data-cy=status-code]')
|
||||
.contains('404')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -29,8 +29,8 @@ describe('Pages > /application/users/[userId]', () => {
|
||||
'refresh-token'
|
||||
)
|
||||
cy.visit(`/application/users/123`, { failOnStatusCode: false })
|
||||
.location('pathname')
|
||||
.should('eq', '/404')
|
||||
.get('[data-cy=status-code]')
|
||||
.contains('404')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -45,15 +45,6 @@ export const usePagination = <T extends PaginationItem>(
|
||||
const fetchState = useRef<FetchState>('idle')
|
||||
const afterId = useRef<number | null>(null)
|
||||
|
||||
const updateAfterId = (newItems: T[]): void => {
|
||||
if (!inverse) {
|
||||
afterId.current =
|
||||
newItems.length > 0 ? newItems[newItems.length - 1].id : null
|
||||
} else {
|
||||
afterId.current = newItems.length > 0 ? newItems[0].id : null
|
||||
}
|
||||
}
|
||||
|
||||
const nextPageAsync: NextPageAsync = useCallback(
|
||||
async (query) => {
|
||||
if (fetchState.current !== 'idle') {
|
||||
@ -68,21 +59,33 @@ export const usePagination = <T extends PaginationItem>(
|
||||
const { data: newItems } = await api.get<T[]>(
|
||||
`${url}?${searchParameters.toString()}`
|
||||
)
|
||||
updateAfterId(newItems)
|
||||
if (!inverse) {
|
||||
afterId.current =
|
||||
newItems.length > 0 ? newItems[newItems.length - 1].id : null
|
||||
} else {
|
||||
afterId.current = newItems.length > 0 ? newItems[0].id : null
|
||||
}
|
||||
setItems((oldItems) => {
|
||||
const updatedItems = inverse
|
||||
? [...newItems, ...oldItems]
|
||||
: [...oldItems, ...newItems]
|
||||
const unique = updatedItems.reduce<T[]>((accumulator, item) => {
|
||||
const isExisting = accumulator.some(
|
||||
(itemSome) => itemSome.id === item.id
|
||||
)
|
||||
if (!isExisting) {
|
||||
accumulator.push(item)
|
||||
}
|
||||
return accumulator
|
||||
}, [])
|
||||
if (cacheKey != null) {
|
||||
savePaginationCache(cacheKey, updatedItems)
|
||||
savePaginationCache(cacheKey, unique)
|
||||
}
|
||||
return updatedItems
|
||||
return unique
|
||||
})
|
||||
setHasMore(newItems.length > 0)
|
||||
fetchState.current = 'idle'
|
||||
},
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- We don't want infinite loops with updateAfterId
|
||||
[api, url, inverse, cacheKey]
|
||||
)
|
||||
|
||||
@ -106,13 +109,18 @@ export const usePagination = <T extends PaginationItem>(
|
||||
afterId.current = null
|
||||
setItems([])
|
||||
} else {
|
||||
fetchState.current = 'loading'
|
||||
const newItems = getPaginationCache<T>(cacheKey)
|
||||
setItems(newItems)
|
||||
updateAfterId(newItems)
|
||||
if (!inverse) {
|
||||
afterId.current =
|
||||
newItems.length > 0 ? newItems[newItems.length - 1].id : null
|
||||
} else {
|
||||
afterId.current = newItems.length > 0 ? newItems[0].id : null
|
||||
}
|
||||
fetchState.current = 'idle'
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- We don't want infinite loops with updateAfterId
|
||||
}, [cacheKey])
|
||||
}, [cacheKey, inverse])
|
||||
|
||||
return { items, hasMore, nextPage, resetPagination, setItems }
|
||||
}
|
||||
|
@ -68,10 +68,7 @@ export const getServerSideProps = authenticationFromServerSide({
|
||||
const guildId = Number(context?.params?.guildId)
|
||||
if (isNaN(channelId) || isNaN(guildId)) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/404',
|
||||
permanent: false
|
||||
}
|
||||
notFound: true
|
||||
}
|
||||
}
|
||||
const { data: guildMember } = await api.get(`/guilds/${guildId}`)
|
||||
|
@ -63,10 +63,7 @@ export const getServerSideProps = authenticationFromServerSide({
|
||||
const guildId = Number(context?.params?.guildId)
|
||||
if (isNaN(channelId) || isNaN(guildId)) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/404',
|
||||
permanent: false
|
||||
}
|
||||
notFound: true
|
||||
}
|
||||
}
|
||||
const { data: guildMember } = await api.get<GuildMember>(
|
||||
@ -74,10 +71,7 @@ export const getServerSideProps = authenticationFromServerSide({
|
||||
)
|
||||
if (!guildMember.member.isOwner) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/404',
|
||||
permanent: false
|
||||
}
|
||||
notFound: true
|
||||
}
|
||||
}
|
||||
const { data: selectedChannelData } = await api.get(
|
||||
|
@ -49,10 +49,7 @@ export const getServerSideProps = authenticationFromServerSide({
|
||||
const guildId = Number(context?.params?.guildId)
|
||||
if (isNaN(guildId)) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/404',
|
||||
permanent: false
|
||||
}
|
||||
notFound: true
|
||||
}
|
||||
}
|
||||
const { data: guildMember } = await api.get(`/guilds/${guildId}`)
|
||||
|
@ -43,10 +43,7 @@ export const getServerSideProps = authenticationFromServerSide({
|
||||
const guildId = Number(context?.params?.guildId)
|
||||
if (isNaN(guildId)) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/404',
|
||||
permanent: false
|
||||
}
|
||||
notFound: true
|
||||
}
|
||||
}
|
||||
const { data: guildMember } = await api.get<GuildMember>(
|
||||
|
@ -38,10 +38,7 @@ export const getServerSideProps = authenticationFromServerSide({
|
||||
const userId = Number(context?.params?.userId)
|
||||
if (isNaN(userId)) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/404',
|
||||
permanent: false
|
||||
}
|
||||
notFound: true
|
||||
}
|
||||
}
|
||||
const { data } = await api.get(`/users/${userId}`)
|
||||
|
@ -16,7 +16,7 @@ export class Authentication {
|
||||
constructor(tokens: Tokens, disableSocketIO: boolean = false) {
|
||||
this.tokens = tokens
|
||||
this.accessTokenAge = Date.now()
|
||||
if (disableSocketIO || typeof window === 'undefined') {
|
||||
if (typeof window === 'undefined' || disableSocketIO) {
|
||||
this.socket = undefined
|
||||
} else {
|
||||
this.socket = io(API_URL, {
|
||||
|
@ -25,8 +25,7 @@ export const AuthenticationProvider: React.FC<
|
||||
const [user, setUser] = useState<UserCurrent>(props.authentication.user)
|
||||
|
||||
const authentication = useMemo(() => {
|
||||
const disableSocketIO = typeof window === 'undefined'
|
||||
return new Authentication(props.authentication.tokens, disableSocketIO)
|
||||
return new Authentication(props.authentication.tokens)
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- We only want to run this memo once
|
||||
}, [])
|
||||
|
@ -54,10 +54,13 @@ export const authenticationFromServerSide = (
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let data = {}
|
||||
let data: any = {}
|
||||
if (fetchData != null) {
|
||||
data = await fetchData(context, api)
|
||||
}
|
||||
if (data.notFound != null) {
|
||||
return data
|
||||
}
|
||||
return { props: data }
|
||||
}
|
||||
} else {
|
||||
@ -71,7 +74,7 @@ export const authenticationFromServerSide = (
|
||||
} else {
|
||||
try {
|
||||
let data: any = {}
|
||||
const authentication = new Authentication(tokens, true)
|
||||
const authentication = new Authentication(tokens)
|
||||
const { data: currentUser } = await authentication.api.get<
|
||||
unknown,
|
||||
AxiosResponse<UserCurrent>
|
||||
@ -79,7 +82,7 @@ export const authenticationFromServerSide = (
|
||||
if (fetchData != null) {
|
||||
data = await fetchData(context, authentication.api)
|
||||
}
|
||||
if (data.redirect != null) {
|
||||
if (data.notFound != null) {
|
||||
return data
|
||||
}
|
||||
return {
|
||||
@ -87,10 +90,7 @@ export const authenticationFromServerSide = (
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/404',
|
||||
permanent: false
|
||||
}
|
||||
notFound: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user