I have a page with a dashboard which is a server component :
import PayoutsTableHandler from './payoutsTableHandler'
import { getServerSession } from 'next-auth'
import { authOptions } from '../../../../../pages/api/auth/[...nextauth]'
import { fetchServerSide } from '../../../../../apiUtils/utils'
import {
GET_HISTORY_FILTERS_PAYOUTS,
GET_HISTORY_TABLE,
} from '../../../../../apiUtils/costants'
const PayoutTableProvider = async () => {
const session = await getServerSession(authOptions)
const historyFilters = await fetchServerSide({
token: session?.user?.access_token,
endpoint: GET_HISTORY_FILTERS_PAYOUTS,
})
const historyTable = await fetchServerSide({
token: session?.user?.access_token,
endpoint: GET_HISTORY_TABLE,
cache: 'no-cache',
tags: ['payouts_table'],
})
const onFilterChange = async ({ dates: { from, to }, status, page }) => {
'use server'
const statusSearchParam = status.slug !== 0 ? status.slug : ''
const formattedUtcFrom = from ? from.toISOString() : ''
const formattedUtcTo = to ? to.toISOString() : ''
return await fetchServerSide({
token: session?.user?.access_token,
endpoint: `${GET_HISTORY_TABLE}?status=${statusSearchParam}&page=${page}&date_from=${formattedUtcFrom}&date_to=${formattedUtcTo}`,
})
}
return (
<PayoutsTableHandler
filters={historyFilters}
data={historyTable}
onFilterChange={onFilterChange}
/>
)
}
export default PayoutTableProvider
Everything seems to be working fine, i can filter with the server action and get the filtered data on the table ( with a useState hook ) and so on.
The problem comes when i open a sideModal ( client component ) that is related with one row (so one element) of the table.
In this sideModal i can upload a document (client component ) and once i've done that i would like to close the modal and revalidate the table fetching the latest data with the status change ( i've uploaded a component ).
This is the client component that uploads a document in the sideModal
import { payoutTypes } from '../../constants'
import { useDropzone } from 'react-dropzone'
import ButtonCVA from 'components/ui/buttonCVA'
import { useSidePanelContext } from '../../../../../context/sidePanelContextProvider'
import {
postAuthenticatedAPI,
putAuthenticatedApi,
} from '../../../../../apiUtils/utils'
import {
GET_INVOICE_DATA,
POST_FILE_UPLOAD_SEEDJ2,
} from '../../../../../apiUtils/costants'
import { useCallback, useState } from 'react'
import { useMessageContext } from '../../../../../context/messageContextProvider'
import { useSession } from 'next-auth/react'
import Image from 'next/image'
import { revalidateTable } from '../../serverActions'
const PayoutFooter = ({ type, data }) => {
switch (type) {
case payoutTypes.PAID:
return <SimpleFooter />
case payoutTypes.REQUIRED:
return <UploadFooter data={data} />
case payoutTypes.REJECTED:
return <UploadFooter data={data} rejected />
case payoutTypes.PROCESSING:
return <SimpleFooter />
default:
return null
}
}
export default PayoutFooter
const UploadFooter = ({ data, rejected }) => {
const { closeSidePanel } = useSidePanelContext()
const { setMessageState } = useMessageContext()
const [loading, setIsLoading] = useState(false)
const { data: session } = useSession()
const onUploadInvoice = useCallback(async (acceptedFiles) => {
let formData = new FormData()
const file = acceptedFiles[0]
formData.append('file', file)
const onSuccess = () => {
revalidateTable()
setMessageState({
type: 'success',
message: {
message: 'Invoice uploaded',
},
})
window?.location.reload()
closeSidePanel()
}
const onError = (res) => {
console.error(res)
setMessageState({
type: 'error',
message: {
message: 'Something went wrong when uploading the track',
},
})
}
try {
setIsLoading(true)
const id = await postAuthenticatedAPI({
payload: formData,
token: session?.user?.access_token,
endpoint: POST_FILE_UPLOAD_SEEDJ2,
formData: true,
})
if (id) {
const res = await putAuthenticatedApi({
payload: { invoice: id },
token: session?.user?.access_token,
endpoint: GET_INVOICE_DATA(data.id),
})
onSuccess(res)
}
} catch (error) {
onError(error)
} finally {
setIsLoading(false)
}
}, [])
const { getRootProps } = useDropzone({
accept: {
'application/pdf': ['.pdf'],
'image/jpg': ['.jpg', '.jpeg', '.png'],
'application/vnd.ms-excel': ['.xls', '.xlsx'],
},
multiple: false,
noDrag: true,
onDrop: onUploadInvoice,
})
return (
<div className="flex flex-col items-center gap-4">
<ButtonCVA
{...getRootProps({ className: 'dropzone' })}
variant={'white'}
size={'medium'}
className={'w-fit'}
srcImg="/images/uploadIconBlack.svg"
imgPosition={'left'}
imgClassName="w-5 h-5"
disabled={loading}
>
{loading ? (
<Image
src="/images/loading.svg"
alt="loading"
height="24"
width="25"
className="h-[24px] w-[24px] animate-spin "
/>
) : (
`Upload ${rejected ? 'new' : ''} invoice`
)}
</ButtonCVA>
<p className="cursor-pointer text-zinc-50" onClick={closeSidePanel}>
Go back
</p>
</div>
)
}
const SimpleFooter = () => {
const { closeSidePanel } = useSidePanelContext()
return (
<ButtonCVA
variant={'white'}
size={'large'}
className={'m-auto mt-4'}
onClick={closeSidePanel}
>
Close
</ButtonCVA>
)
}
To revalidate the tag of the ssr fetch call i use a server action created in a separate file that i call on the onSuccess callback
'use server'
import { revalidateTag } from 'next/cache'
export const revalidateTable = () => {
revalidateTag('payouts_table')
}
Now, if i use router.refresh() also in combination with router.push() on the onSuccess callback i cannot refresh the page and i need to manually refresh the page to get the latest data. Now as you can see in the code i need to do a window?.location.reload() which is ugly for me and it's working.
Any idea why here the router.refresh() might not be working?
I also tried putting it inside a startTransition hook but nothing.
Ok, little update, using some console i see that the ssr components rerenders with the correct data but the children that receives the data as prop don't.
In this case the children is
return (
<PayoutsTableHandler
filters={historyFilters}
data={historyTable}
onFilterChange={onFilterChange}
/>
)
and the
historyTable
has the updated values but the PayoutsTableHandler doesn't rerender and it doesn't display the new data then