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

Cannot upload to multiple FilePond instances with controlled state (state is cleared after attempting second upload) #153

Closed
cheryllium opened this issue Oct 4, 2020 · 16 comments

Comments

@cheryllium
Copy link

Describe the bug
Uploading to multiple FilePond components with controlled state fails; the state for all FilePond components is cleared once you try to upload to more than one component.

Logs
There are no errors in the browser console or dev server logs.

To Reproduce
See this sandbox: https://codesandbox.io/s/react-filepond-forked-cvck1?file=/src/App.js

  1. Upload any file to the first FilePond component. (This appears to succeed.)
  2. Upload any file to the second FilePond component. (This appears to succeed for a split second, but then both FilePond components become empty and state is cleared.)

Expected behavior
I expect both FilePond components to keep the uploaded file, and to update the controlled state correctly.

Information about your project:

  • Firefox 79.0 for Ubuntu
  • ElementaryOS
  • FilePond version (latest; issue was reproduced with given sandbox)

Additional context
I'm not certain that this is a glitch with FilePond. It's possible that this is a logic error on my end. However, I'm quite stumped as to what is the correct way to have multiple controlled FilePond components. The code I have seems to control state correctly with a single FilePond component, and seems to also work with non-FilePond components (other form fields like text, input, etc) - which led me to believe this could be a FilePond issue, but I'm not sure. I would appreciate any enlightenment on this issue; if I'm doing it improperly, could you please let me know what is the proper way? And thank you for your work on this great library :)

@rikschennink
Copy link
Collaborator

It looks like formData is always empty

{foo: undefined, bar: undefined}

so I'm not sure this is related to FilePond

@cheryllium
Copy link
Author

@rikschennink I just updated the sandbox with a useEffect() hook on formData so that you can see that the state does update. Try opening the console, and observe what happens when you upload a file to the first component, and then the second. You can also add a console.log() to the event handlers on each FilePond component. It does update the state, but for some reason an upload to bar seems to trigger the foo component's event handler if you had already uploaded a file to foo.

It is possible it's a problem not with FilePond but just my understanding of it. Is it possible to have multiple controlled FilePond components on the same page, and if so, how? That's my goal, but I've been going at it for a full day now and I haven't figured it out.

@rikschennink
Copy link
Collaborator

Yes that should be possible. I don't know why the formData is being reset to undefined, I don't have enough experience with hooks.

@cheryllium
Copy link
Author

Okay, glad to hear it should be possible. I've decided to use something else in our project since this doesn't seem to be possible at the moment, but I hope you guys figure it out, it is a beautiful component. Thanks for taking a look. :)

@sweetliquid
Copy link

This https://codesandbox.io/s/react-filepond-forked-skdbc is the repaired version, it can work normally.

@sweetliquid
Copy link

The reason is that for updateFormState(), the value of formData is always {foo: undefined, bar: undefined}.

Please refer to:
Why am I seeing stale props or state inside my function?
Should I use one or many state variables?

@cheryllium
Copy link
Author

@sweetliquid Oh wow, thank you so much!! Now I've got it. Thank you for the help <3

@Rafcin
Copy link

Rafcin commented Dec 3, 2020

I'm having a similar issue where I have a multistep Formik form using react-albus for each step and i've created a custom class the holds with all my configurations so I can reuse it and then a component I use inside Formik. I 3 times in my Formik form and each has a different name and the name of the file state in is the name passed in the settings [this.props.settings.name]: [] However regardless of what I do after uploading a second file they all just vanish.

Reusable Filepond:

       <FilePond
          type="file"
          name={this.props.settings.name}
          ref={ref => this.pond = ref}
          files={this.state[this.props.settings.name]}
          allowImagePreview={this.props.settings.allowImagePreview}
          allowMultiple={this.props.settings.allowMultiple}
          allowReorder={this.props.settings.allowReorder}
          maxFiles={this.props.settings.maxFiles}
          allowImageEdit={this.props.settings.allowImageEdit}
          acceptedFileTypes={this.props.settings.acceptedFileTypes}
          allowFileTypeValidation={this.props.settings.allowFileTypeValidation}
          allowFileSizeValidation={this.props.settings.allowFileSizeValidation}
          maxFileSize="2MB"
          labelMaxFileSizeExceeded="Files can not be larger than 2MB"
          imageEditEditor={create({
            storageName: 'doka',
            utils: ['crop', 'filter', 'color', 'markup', 'sticker'],
            stickers: ['']
          })}
          onupdatefiles={fileItems => {
            this.setState({
              [this.props.settings.name]: fileItems.map(fileItem => fileItem.file)
            }, () => this.props.onupdatefiles(this.state[this.props.settings.name]));
          }}
        />

The Form Component:

      {console.log("FUCKING VALUES", values[props.name])}
      <ReusablePond
        settings={{
          onChange: handleChange,
          onBlur: handleBlur,
          name: props.name,
          id: props.name,
          allowImageEdit: true,
          allowImagePreview: true,
          allowMultiple: true,
          allowReorder: true,
          acceptedFileTypes: props.acceptedFileTypes,
          allowFileTypeValidation: true,
          allowFileSizeValidation: true,
          maxFiles: props.max,
        }}
        
        onupdatefiles={(e) => {
          if(_isMounted.current){
            //setFieldValue(props.name, e)
            console.log("Mounted File", e)
          }
        }}
      />

The Use:

     <FormFileUpload name="test_restaurant_menu_item_imgs" title="Test Special Photos"
         max={5} acceptedFileTypes={['image/jpeg']}
       />

@Rafcin
Copy link

Rafcin commented Dec 3, 2020

@michaelcuneo
Copy link

@Rafcin same here... all of my FilePond's are wrapped in a Formik component. I can add posts, edit posts, and remove posts, with my component, only if I make sure that I add the images as the very last step... as soon as I edit any other input field inside the Formik component, Filepond drops it's images, and/or resets to the initial image state. (If it had images already i.e. editing).

This has been a concern of mine for almost 2 years now. No solution. I've tried everything.

onupdatefiles appears to bork the Formik wrapper so I removed it altogether, because finding a way for them to sync with each other appears impossible.

                  <FilePond
                    id="files"
                    name="files"
                    key="files"
                    ref={filePondGallery}
                    server={{
                      load: (source, load) => {
                        const myRequest = new Request(source);
                        fetch(myRequest).then(response => {
                          response.blob().then(myBlob => {
                            load(myBlob);
                          });
                        });
                      },
                    }}
                    instantUpload={false}
                    upload="false"
                    files={files.files}
                    onupdatefiles={files => { console.log(files); onUpdateFiles(setFieldValue, files)}}
                    allowMultiple
                  />

If I load up a filepond component and feed it multiple initial files... i.e. for example 3.

Hit the top left X on one file inside the component, it instantly removes it then puts it back, because it's running it's own management of files and can't be taken control of... as soon as you add 'onupdatefiles' to the Filepond component, it becomes uncontrolled and ignores the control, so if you control it, it ignores 'onupdatefiles', and becomes controlled. It's an unusual situation.

But to add to this, my component refuses to work unless onupdatesfiles exists... but does nothing. i.e.
onupdatefiles = {() ={}}

Wish there was an acceptable working flow for this.

@braxeatssnacks
Copy link

braxeatssnacks commented Jun 1, 2023

Hey folks! Sorry to chime in on a very old, stale thread.

That said, I've shared similar struggles recently and came across this as the closest example of what I was experiencing trying to wire up FilePond through Formik. Some 2 years, later I've no doubt you've figured out a solution @michaelcuneo, but here's to hoping this post proves helpful for any poor chump who ends up in a similar situation.

Ultimately, the pattern I was looking to achieve was to get the filepond react wrapper to be uncontrolled with a default value. From what I can see @rikschennink thought through this with a little trick to turn the "controlled" pattern on via the presence of the onfileupdates prop. You can see inside of shouldComponentUpdate how this behavior is toggled after any re-render:

if (!this.allowFilesSync) {
this.allowFilesSync = true;
return false;

However, this "default value" isn't necessarily available on first render in my case. (My form needs to be "resumable" — and therefore needs to rehydrate any user data that has been populated prior.)

Ultimately, I achieved the desired result by manually manipulating the component instance variable allowFilesSync. This is (no doubt inadvertently) exposed through the component's ref. Here's what this looks like for me where the formik value stored is the processed file(s) serverId:

const MyFormikFileInput = ({ name, multiple }) => {
  const [field, meta, helpers] = useField<string | string[]>({ name, multiple });
  const filepond = useRef<Nullable<FilePondInput>>(null);
  const setRef = (instance: Nullable<FilePondInput>) => {
    filepond.current = instance;
    /**
     * Ensure that after the initial value is present, we "convert" filepond to uncontrolled.
     * This ensures that the "files" prop is treated as though it were `defaultValue`.
     */
    if (filepond.current && meta.initialValue) {
      filepond.current.allowFilesSync = false;
    }
  };

  /** Provided all updated files update field in form state. */
  const setFiles = (files: Optional<FilePondFile[]>) => {
    const serverIds = files?.map(({ serverId }) => serverId).filter(Boolean);
    helpers.setValue(multiple ? serverIds : serverIds?.[0]);
    helpers.setTouched(true);
  };

  // Interpret any initial value as hydrated server ids to uploaded files
  const initialFiles: Optional<FilePondInitialFile[]> = initialValue?.length
    ? ([] as string[]).concat(initialValue ?? []).map((fileId) => ({
        source: fileId,
        options: {
          type: 'local',
        },
      }))
    : undefined;

  return (
    <FilePondInput
      name={field.name}
      allowMultiple={multiple}
      files={initialFiles}
      onprocessfiles={() => setFiles(filepond.current?.getFiles())}
      onupdatefiles={setFiles}
      ref={setRef}
      {...otherConfig}
    />
  );
};

As allowFilesSync is not formally exposed through the component's interface, I expect it will undoubtedly break should @rikschennink make any substantial changes to this component in future updates.

I guess I'll cross that bridge when/if we get there, but I hope this proves helpful in terms of future consideration or current mitigation! 😄

@rikschennink
Copy link
Collaborator

@braxeatssnacks thanks for the detailed explanation on how to work around these limitations :)

It is indeed a workaround to make FilePond "work" with external state.

For version 5 of FilePond I'm regularly testing with various JS Frameworks to make sure it "just" works.

@michaelcuneo
Copy link

I can't remember what my solution was for this, but I have since switched to Svelte and SvelteKit which seem to work a lot stabler with state.

@rikschennink
Copy link
Collaborator

@michaelcuneo ❤️ Svelte, I used it to built Pintura and Hotlist and it's gonna power the FilePond v5 view as well.

@michaelcuneo
Copy link

Nice! @rikschennink I switched over to Svelte about 2 years ago and haven't looked back. Our entire public facing laboratory with Administrative backend including 'Filepond' for the images, is all SvelteKit. Went from 18,000 lines of React to 1800 lines of Svelte, and 100/100 for all Chrome Lighthouse tests, (in the next version). It's bloody good how you can pure library now with Svelte and make a component like FilePond.

https://fastlab.soci.org.au/

@rikschennink
Copy link
Collaborator

@michaelcuneo Olala, love those abstract video headers 🔥

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants