Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

onFileUploadSuccess called multiple times #53

Open
barbalex opened this issue Nov 5, 2024 · 1 comment
Open

onFileUploadSuccess called multiple times #53

barbalex opened this issue Nov 5, 2024 · 1 comment
Labels
bug Something isn't working

Comments

@barbalex
Copy link

barbalex commented Nov 5, 2024

Describe the bug

First I got multiple mentions from users that files had been uploaded multiple times (one to about 6 times, varying). But I couldn't provoke it myself.

Today I happened to see it while developing: onFileUploadSuccess was called two times while uploading a single file. Both times returning the same info.uuid. Unfortunately I forgot to make a screenshot.

So I had to implement catching duplicates, see infoUuidsProcessed in the code below.

Sorry I can't build a reproducable example. So feel free to close this issue. I simply wanted to document it.

Expected behavior

onFileUploadSuccess should only occur once per file uploaded.

Code / screenshots

This is my component:

import {
  useCallback,
  useState,
  useRef,
  useContext,
  memo,
  useMemo,
  Suspense,
} from 'react'
import { observer } from 'mobx-react-lite'
import { useApolloClient, useQuery, gql } from '@apollo/client'
import styled from '@emotion/styled'
import upperFirst from 'lodash/upperFirst'
import Button from '@mui/material/Button'
import IconButton from '@mui/material/IconButton'
import SimpleBar from 'simplebar-react'
import {
  FaPlus,
  FaMinus,
  FaEye,
  FaEyeSlash,
  FaChevronLeft,
  FaChevronRight,
} from 'react-icons/fa6'
import { useNavigate, useLocation, Outlet, useParams } from 'react-router-dom'

import './index.css'

import { ErrorBoundary } from '../ErrorBoundary.jsx'
import { Error } from '../Error'
import { Spinner } from '../Spinner'
import { MenuBar } from '../MenuBar/index.jsx'

import {
  apFile as apFileFragment,
  idealbiotopFile as idealbiotopFileFragment,
  popFile as popFileFragment,
  tpopFile as tpopFileFragment,
  tpopkontrFile as tpopkontrFileFragment,
  tpopmassnFile as tpopmassnFileFragment,
} from '../fragments'
import { Uploader } from '../Uploader/index.jsx'
import { UploaderContext } from '../../../UploaderContext.js'
import { File } from './Files/File.jsx'
import { isImageFile } from './isImageFile.js'
import { StoreContext } from '../../../storeContext.js'
import { icon } from 'leaflet'

const Container = styled.div`
  flex-grow: 1;
  display: flex;
  flex-direction: column;
`
const OutletContainer = styled.div`
  flex-grow: 1;
`

const fragmentObject = {
  ap: apFileFragment,
  idealbiotop: idealbiotopFileFragment,
  pop: popFileFragment,
  tpop: tpopFileFragment,
  tpopkontr: tpopkontrFileFragment,
  tpopmassn: tpopmassnFileFragment,
}

export const FilesRouter = memo(
  observer(({ parentId = '99999999-9999-9999-9999-999999999999', parent }) => {
    const store = useContext(StoreContext)
    const { fileId } = useParams()
    const navigate = useNavigate()
    const { pathname } = useLocation()
    const isPreview = pathname.endsWith('Vorschau')
    const client = useApolloClient()
    const uploaderCtx = useContext(UploaderContext)
    const api = uploaderCtx?.current?.getAPI?.()
    const infoUuidsProcessed = useRef([])

    const containerRef = useRef(null)

    const queryName = `all${upperFirst(parent)}Files`
    const parentIdName = `${parent}Id`
    const fields = `${upperFirst(parent)}FileFields`
    const fragment = fragmentObject[parent]

    const query = gql`
        query FileQuery($parentId: UUID!) {
          ${queryName}(
            orderBy: NAME_ASC
            filter: { ${parentIdName}: { equalTo: $parentId } }
          ) {
            nodes {
              ...${fields}
            }
          }
        }
        ${fragment}
      `

    const { data, error, loading, refetch } = useQuery(query, {
      variables: { parentId },
    })

    const files = data?.[`all${upperFirst(parent)}Files`].nodes ?? []

    const onCommonUploadSuccess = useCallback(
      async (info) => {
        // reset infiUuidsProcessed
        infoUuidsProcessed.current = []
        // close the uploader or it will be open when navigating to the list
        api?.doneFlow?.()
        // clear the uploader or it will show the last uploaded file when opened next time
        api?.removeAllFiles?.()
        // somehow this needs to be delayed or sometimes not all files will be uploaded
        setTimeout(() => refetch(), 500)
      },
      [client, fields, fragment, parent, parentId, refetch],
    )

    // ISSUE: sometimes this is called multiple times with the same info.uuid
    const onFileUploadSuccess = useCallback(
      async (info) => {
        if (info) {
          if (infoUuidsProcessed.current.includes(info.uuid)) return
          infoUuidsProcessed.current.push(info.uuid)
          let responce
          try {
            responce = await client.mutate({
              mutation: gql`
              mutation insertFile {
                create${upperFirst(parent)}File(
                  input: {
                    ${parent}File: {
                      fileId: "${info.uuid}",
                      fileMimeType: "${info.mimeType}",
                      ${parent}Id: "${parentId}",
                      name: "${info.name}"
                    }
                  }
                ) {
                  ${parent}File {
                    ...${fields}
                  }
                }
              }
              ${fragment}
            `,
            })
          } catch (error) {
            console.log(error)
            store.enqueNotification({
              message: error.message,
              options: {
                variant: 'error',
              },
            })
          }
          console.log('FilesRouter.onFileUploadSuccess', { info, responce })
        }
      },
      [client, fields, fragment, parent, parentId, refetch],
    )

    const onFileUploadFailed = useCallback((error) => {
      console.error('Upload failed:', error)
      store.enqueNotification({
        message: error?.message ?? 'Upload fehlgeschlagen',
        options: {
          variant: 'error',
        },
      })
      // close the uploader or it will be open when navigating to the list
      api?.doneFlow?.()
      // clear the uploader or it will show the last uploaded file when opened next time
      api?.removeAllFiles?.()
      // somehow this needs to be delayed or sometimes not all files will be uploaded
      setTimeout(() => refetch(), 500)
    }, [])

    const firstFileId = files?.[0]?.fileId

    const onClickPreview = useCallback(
      () => navigate(`${firstFileId}/Vorschau`),
      [firstFileId],
    )
    const onClickClosePreview = useCallback(() => {
      // relative navigation using ../.. does not work here
      const fileIdBeginsAt = pathname.indexOf(fileId)
      const newPathname = pathname.slice(0, fileIdBeginsAt)
      navigate(newPathname)
    }, [pathname, fileId])

    const menus = useMemo(
      () => [
        <IconButton
          key="vorschau_oeffnen"
          title="Vorschau öffnen"
          onClick={onClickPreview}
        >
          <FaEye />
        </IconButton>,
        <IconButton
          key="dateien_hochladen"
          title="Dateien hochladen"
          onClick={api?.initFlow}
        >
          <FaPlus />
        </IconButton>,
      ],
      [onClickPreview],
    )
    const previewMenus = useMemo(
      () => [
        <IconButton
          key="vorschau_schliessen"
          title="Vorschau schliessen"
          onClick={onClickClosePreview}
        >
          <FaEyeSlash />
        </IconButton>,
        <IconButton
          key="dateien_hochladen"
          title="Dateien hochladen"
          onClick={api?.initFlow}
        >
          <FaPlus />
        </IconButton>,
        <IconButton
          key="loeschen"
          title="löschen"
          onClick={() => {
            console.log('TODO: delete. How to know which file?')
          }}
        >
          <FaMinus />
        </IconButton>,
        <IconButton
          key="vorige_datei"
          title="vorige Datei"
          onClick={() => {
            console.log('TODO: navigate. How to know which file?')
          }}
        >
          <FaChevronLeft />
        </IconButton>,
        <IconButton
          key="naechste_datei"
          title="nächste Datei"
          onClick={() => {
            console.log('TODO: navigate. How to know which file?')
          }}
        >
          <FaChevronRight />
        </IconButton>,
      ],
      [onClickClosePreview],
    )

    if (loading) return <Spinner />

    if (error) return <Error error={error} />

    return (
      <ErrorBoundary>
        <Container ref={containerRef}>
          <Uploader
            onFileUploadSuccess={onFileUploadSuccess}
            onFileUploadFailed={onFileUploadFailed}
            onCommonUploadSuccess={onCommonUploadSuccess}
          />
          <MenuBar>{isPreview ? previewMenus : menus}</MenuBar>
          <OutletContainer>
            <Suspense fallback={<Spinner />}>
              <Outlet
                context={{ files, parent, parentId, refetch, containerRef }}
              />
            </Suspense>
          </OutletContainer>
        </Container>
      </ErrorBoundary>
    )
  }),
)

Environment

  • Library version: "@uploadcare/react-uploader": "1.1.0"
  • Language/framework version: react 18.3.1
  • OS version: windows 11 up to date
@barbalex barbalex added the bug Something isn't working label Nov 5, 2024
@metacop
Copy link

metacop commented Dec 9, 2024

I have the same problem but with fileValidators, it is called multiple times, when you upload a file locally, it goes through the function 4 times.
image
and when I upload another file the second one, it happens 6 times.
image
and shows the previous file and then the current one. this breaks all my validation

in onFileAdded 2 times
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants