Client component inside Server Component not rerendering

37 Views Asked by At

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

0

There are 0 best solutions below