primefaces / primereact

The Most Complete React UI Component Library
https://primereact.org
MIT License
6.35k stars 967 forks source link

FileUpload: `maxFiles` and `extensions` validations #4173

Open ImranRawani opened 1 year ago

ImranRawani commented 1 year ago

Describe the feature you would like to see added

https://primereact.org/fileupload/

I referred the above component documentation for version 7,8 and 9. None of these have props available for the user to configure the valid file formats and no of files allowed. I think these are very common scenarios for any UI in any application while having file upload feature.

Is your feature request related to a problem?

No validation for file formats or no of files is given out of the box ;

We do have prop called accept which just filters the files in windows dialog ; user is still free to click all files and then can select any file which is not there is accept - have tested this here https://stackblitz.com/edit/react-ztqd11?file=src%2Fdemo.js . I am able to attach a pdf file while the accept looks like this --> accept=".doc, .docx"

Describe the solution you'd like

I did find maxFileSize prop and corresponding message props like invalidFileSizeMessageDetail & invalidFileSizeMessageSummary.

Similar to this, there should be some props like :

For File format --- > allowedFileTypes (which can be an array of extensions allowed) For No of files --> maxFilesAllowed (which should be a number)

and corresponding errorMessage Props for both of the above like invalidFileErrorMessage and ExceedMaxFilesErrorMessage.

Describe alternatives you have considered

We have custom validation functional component already written which was working fine till 7.2.0 release, but after upgrading to 8.7.3 and even tested in 9.2.1, its broken as we have to pass the ref of the file upload to this component and then we are getting errors like : (based on issues #2893 , #3045 , #3043 - shows as fixed but still does not work in 8.7.3 and 9.2.1 )

ref.current.setstate is not a function ,as in below code

fileUploadComponentRef.current.setState({ files: validFiles });

cannot read show of undefined ( ref.current.messagesUI ) #as in below code

fileUploadComponentRef.current.messagesUI.show({ severity: 'error', summary: , });

Additional context

No response

melloware commented 1 year ago

@ImranRawani you no longer have access to internal state of components that is and was always a bad practice. Now you can access the Files[] collection through the ref

See: https://primereact.org/fileupload/#api.FileUpload.methods

getFiles(), setFiles(File[]), and clear()

You can implement onBeforeSelect and validate number of files, file extensions or anything else you want and return false from that method to not allow that selection to go through. See: https://primereact.org/fileupload/#api.FileUpload.callbacks.onBeforeSelect

ImranRawani commented 1 year ago

@melloware , agree , can you please let me know as to what is the minimum version required to be used to use setFiles ?

melloware commented 1 year ago

Looks like in 9.0.0+ https://github.com/primefaces/primereact/issues/3750

ImranRawani commented 1 year ago

also, based on issues https://github.com/primefaces/primereact/issues/2893 , https://github.com/primefaces/primereact/issues/3045 , https://github.com/primefaces/primereact/pull/3043 - shows as fixed but still does not work in 8.7.3 and 9.2.1 - I mean ref does not give access to setState and messagesUI of fileUpload component.

So, going by your first comment - you no longer have access to internal state of components that is and was always a bad practice. Now you can access the Files[]

so, why those issues 2893, 3045 and 3043 are marked as fixed and closed , if ref still does not give access to these props ?

melloware commented 1 year ago

Those issues are closed because now the FileUpload exposes only what PrimeReact wants to expose on the ref and not access to all methods and props inside the ref.

Here is my example where I do validation and prevent the file from being selected by both "number of files" and certain file extensions.

const onBeforeSelect = (e: FileUploadSelectParams) => {
    let event = e.originalEvent;
    const allowSelect = event.target.files && event.target.files.length === 1;
    if (!allowSelect) {
        setErrorMessage(`Only <strong>1</strong> file may be uploaded at a time and you attempted to upload <strong>${files.length}</strong> files.`);
        return false;
    }
    const fileName = event .target.files[0].name.toLocaleLowerCase();
    if (!FileService.isExtension(fileName)) {
        setErrorMessage(`Only proper file types are allowed to be uploaded and <strong>"${fileName}"</strong> is not valid.`);
        return false;
    }
}
ImranRawani commented 1 year ago

@melloware , thanks , will try this out and update, but when you have everything ready, why you cannot give it as inbuilt feature by asking the user to pass props as I suggested (in Describe the solution you'd like) - that would be much more user friendly for any developer as nobody can dream of setErrorMessage which you have used in your above example as its not there in the documentation (at least does not show up in the search results both entire documentation search as well as searching in the file upload documentation for 9.2.1 )

melloware commented 1 year ago

@ImranRawani I don't work for PrimeTek I am a developer just like you so not sure when you say "why you cannot give".... Its not up to me. But I have an advanced File Upload working with the current features available and haven't found anything that I can't do.

ImranRawani commented 1 year ago

ohh....sorry, did not know , saw your post in many issues and the way you replied here , so thought you are working with Primetek

ImranRawani commented 1 year ago

@melloware , Also, if I understood correctly from other places, the approach you have given will not work in some scenarios like

if 10 files are allowed and user selected 8 valid files and 2 invalid files (either wrt size or format) , returning false from onBeforeSelect will discard the entire selection. What I want is 8 files should be selected finally and 2 will be discarded showing an error message for both files that they were invalid due to whatever reason like size or format, etc.

Please correct me if I am wrong or provide any alternate solution for above scenario, if you have it ?

melloware commented 1 year ago

Well you will have to try it but you could get the 8 valid files and call ref.current.setFiles(validFiles) in theory.

ImranRawani commented 1 year ago

ok sure, will try this out, but can you tell me how did you know about setErrorMessage - is there any documentation for the same ?

and is this doing the same thing what we used to achieve till version 7 as in code below :

fileUploadComponentRef.current.messagesUI.show({ severity: 'error', summary: , });

melloware commented 1 year ago

Nope I used my own static Message component: https://primereact.org/message/#severity

ImranRawani commented 1 year ago

ok, but then I can imagine one more issue here - with maxFileSize prop and corresponding message props like invalidFileSizeMessageDetail & invalidFileSizeMessageSummary configured, fileupload component will take care of showing the error message as soon as we select the files in a particular place which I assume is the messagesUI in version 7.2.0

if I have my own message component - I can show there but some error messages like above coming out of the box will come at one place and others which are handled by my custom code will come at another place -which is inconsistent ?

melloware commented 1 year ago

Yep I can see that. I updated your title and marked it as a New Feature.

manuelpoelzl commented 1 year ago

@ImranRawani you no longer have access to internal state of components that is and was always a bad practice. Now you can access the Files[] collection through the ref

See: https://primereact.org/fileupload/#api.FileUpload.methods

getFiles(), setFiles(File[]), and clear()

You can implement onBeforeSelect and validate number of files, file extensions or anything else you want and return false from that method to not allow that selection to go through. See: https://primereact.org/fileupload/#api.FileUpload.callbacks.onBeforeSelect

I found this issue while searching for a solution, I'm just curious, is this somethign that should work in PrimeReact 9.2.2? I tried to use the onBeforeSelect event as described but somehow e.files is always an empty array, even when getting them from the ref with the getFiles() method.

melloware commented 1 year ago

Maybe this will help... the event can either be a DragAndDrop Event or an Input Event so you have to determine which and get the files based on the way its being dropped

const onBeforeSelect = (e: FileUploadSelectParams) => {
    let event = e.originalEvent;
    let files = [];
    if (event.type === 'drop') {
        event = event as DragEvent;
        files = event.dataTransfer?.files!;
    } else {
        event = event as React.ChangeEvent<HTMLInputElement>;
        files = event.target.files!;
    }

    if (files.length <= 0) {
        return false; // retuning false kills this upload 
    }

    // DO YOUR CODE HERE and return either true or false
};