I am using the React Dropzone library for file uploads. I have noticed that the files clear everytime the file box is clicked. For example, if you add one file, then click the box again to add another file, the original disappears. I have tried using the onDrop function in dropzone but I havent been able to figure that our. I am new to React so I would love some pointers on where to find information on this.
react dropzone has an argument name multiple which you can specify if you want to let user select/drag multiple files. the default value for this argument is true so this is how i use the library:
const { getRootProps, getInputProps } = useDropzone({
accept: '.jpeg,.png,.jpg',
onDrop: acceptedFiles => {
if (acceptedFiles.length === 0) {
return;
}
const newFiles = acceptedFiles.map(file => {
return {
file,
preview: URL.createObjectURL(file),
};
});
let newFilesState = [...files.concat(newFiles)];
//here i add the previously added files to new state and concat them with newly droped files
},
});
here is my jsx
<Button onClick={() => {
inputEl.current.click();
}}
>
<div {...getRootProps()}>
<input
// force input to re-render on file change
{...getInputProps()}
ref={inputEl}
/>
</div>
</Button>
note that variable named files is my redux which stores the previously added files. and after i receive a dropped/selected new file(S) is concat them with my files stored in redux
Related
I just want to create simple file upload component on react. It will be something simple that after select the file and submit the upload button, it will give a massage with filename like "You uploaded fileName.txt". I have tried search so many sources but it's mostly more than I need, that would be great if anyone could help me about it. Thank you!
You can detect file selection using the onChange event listener, and you can access the name of the selected file via e.target.files which returns all of the files which have been selected, we'll take the first selected file and store its name value inside a state called selectedFileName.
during render we'll check if the selectedFileName state has value, we'll display it inside a p tag.
import React, { useState } from 'react';
export const App = () => {
const [selectedFileName, setSelectedFileName] = useState('');
const [displayFileName, setDisplayFileName] = useState(false);
const submitFile = () => setDisplayFileName(true);
const handleFileSelect = e => {
const selectedFile = e.target.files[0];
setDisplayFileName(false);
setSelectedFileName(selectedFile?.name);
};
return (
<div>
<input
type='file'
accept='*'
name='fileSelect'
onChange={handleFileSelect}
/>
{displayFileName ? (
<p>{ selectedFileName?`You Uploaded '${selectedFileName}'`:"Please select a file!"}</p>
) : (
<button value='Submit' onClick={submitFile}>
Submit File
</button>
)}
</div>
);
};
I am building a website targeted mostly at browsers using Ionic React.
I am trying to use a react-hook-form to upload a list of files (among other data) and save them in a FieldArray together with other data.
I have implemented file upload following this answer in Ionic Forum, using an IonButton and an input.
<input
type="file"
id="file-upload"
style={{ display: "none" }}
onChange={() => { setFile(index);}}/>
<IonButton
onClick={() => {openFileDialog();}}
disabled={files === undefined || files[index] === undefined}>
<IonLabel>Upload file</IonLabel>
<IonIcon slot="start" />
</IonButton>
Code:
function openFileDialog() {
(document as any).getElementById("file-upload").click();
}
const setFile = (index: number) => (_event: any) => {
console.log(`Getting file for index ${index}`);
let f = _event.target.files![0];
var reader = new FileReader();
reader.onload = function () {
setValue(`files.${index}.file`, reader.result);
};
reader.onerror = function () {
console.log("File load failed");
};
reader.readAsDataURL(f);
};
Full example code: codesandbox
The file is correctly uploaded but I have not been able to add it to the correct field in the FieldArray. Files are always added to element 0. I assume this is related to input not been modified directly in the form but in function openFileDialog(). As a result, the function onChange() of input does not receive the correct value of index.
Is it or is there a different source of error?
A solution would be to wait for the file to be loaded in the method onClick() of IonButton but I cannot send the index when calling (document as any).getElementById("file-upload").click();.
Another solution could be to use only one component for file upload instead of two. However, it looks like Ionic does not have a component for this. IonInput type="file" does not work. The documentation is confusing: "file" does not appear in the list of accepted values for property type but it is mentioned in the description of properties multiple and accepted.
How can I save the file correctly?
I found a few issues with your approach, I also removed the reading of the file into a blob, dont think you should do that until the user actually submits since they could delete the file.
First Issue - you were not passing index into this function
const setFile = (index: number, _event: any) => {
methods.setValue(`files[${index}].file` as any, _event.target.files[0]);
methods.setValue(
`files[${index}].title` as any,
_event.target.files[0]?.name
);
};
The second issue, you were not creating unique identifiers for the upload button, you need to include the index there also
<IonItem>
<input
type="file"
id={`file-upload-${index}`} // <== NEW CODE
style={{ display: "none" }}
onChange={(e) => {
console.log("In input", index);
setFile(index, e);
}}
/>
<IonButton
onClick={() => {
openFileDialog(index); // <== NEW CODE
}}
disabled={files === undefined || files[index] === undefined}
>
<IonLabel>Upload file</IonLabel>
<IonIcon slot="start" />
</IonButton>
</IonItem>
and then you need the index in openFileDialog so you can click the appropriate button.
function openFileDialog(index: any) {
(document as any).getElementById("file-upload-" + index).click();
}
See the complete solution here in this sandbox
https://codesandbox.io/s/ionic-react-hook-form-array-with-file-input-nq7t7
I am trying to grab the text that is typed into the wysiwig to store it into my database.
I have tried a lot of different stuff and this was my most recent iteration:
<EditorContent editor={editor} className="contentInput" style={{ backgroundColor:'lightgrey' }} value={state.content} onChange={(evt) => setState({ ...state, content: evt.target.value })}/>
and with that I am getting the error that value is not a property on target. I believe this is because it is no longer an HTML input element, but I am not sure how I would now grab that content to put in the database?
const editor = useEditor({
// ...
onUpdate({ editor }) {
setState(editor.getJSON());
},
});
First you need to create a state with useState hook, then create the editor with useEditor Hook, inside it you're going to to add event onUpdate and set the value of the editorContent state on every update on the editor.
const [editorContent, setEditorContent] = useState("");
const editor = useEditor({
extensions: [StarterKit],
content: "<p>Hello World! 🌎️</p>",
onUpdate({ editor }) {
setEditorContent(editor.getHTML());
},
});
const msg = editor?.getText();
console.log("msg", msg);
In console you'll get the normal text which is written in tiptap input field.
Use optional chaining because editor initially return null and you might get error of not reading value of undefined.
I am trying to store an uploaded file in a state variable that I will post to the DB once the user has completed the rest of the form. For some reason, my state wont update and my state variable is left null. I am calling onChange and passing it my onChange function to store the file in state. I put some comments in the code. Can anyone help please?
interface PostState {
file: File
//i have tried multiple data types for the file variable, still no luck
}
const Form = (props) => {
const defaultPostState: PostState = {
file: null,
//i have also tried setting this to undefined or not setting a default value at all as well
}
const [postState, setPostState] = useState(defaultPostState)
const onChange = (e) => {
setPostState({...postState, file: e.target.files[0]})
//this is what wont work - i have debugged it and logged it, it wont store the target file in file
}
<div className='form-group'>
<label>Upload file</label>
<input
id='file'
className='form-control-file'
type='file'
name='file'
ref={register}
onChange={onChange}
/>
</div>
I would like to upload multiple images to send them off.
I tried this two ways in handleChange but the formdata is always empty.
I also want to know how to display the image in the react
state = { files: []}
fileChangeHandler = (e) => {
this.setState({ files: e.target.files })
this.setState({ files: [...this.state.files, ...e.target.files] })}
handleSubmit(event) {let fileData = new FormData();
fileData.append('files', this.state.files);
uploadClaimFile(response.data.id, fileData);}
the input
<input type="file" multiple onChange={this.fileChangeHandler} />
The safest way to append to a state array without directly modifying it is to make a shallow copy of the array, add the new items, and then replace the array using setState:
fileChangeHandler = (e) => {
const files = [...this.state.files]; // Spread syntax creates a shallow copy
files.push(...e.target.files); // Spread again to push each selected file individually
this.setState({ files });
}
As for uploading the files, when appending to a FormData object you must append the files one at a time:
handleSubmit(event) {
const fileData = new FormData();
this.state.files.forEach((file) => fileData.append('files[]', file));
// ... Submit fileData
}
Note: The use of [] in naming the data is in accordance with PHP naming conventions when uploading multiple files.
Edit
To answer your last question about displaying multiple uploaded files, you would want to write a method to take those files, create URLs that tie them back to the document, and display them. However, created URLs must be revoked to prevent memory leaks. Thus, it might be a good idea to store them in state to keep track of them, so you can implement it like this:
this.state = { files: [], urls: [] };
setFileUrls(files) {
const urls = files.map((file) => URL.createObjectURL(file));
if(this.state.urls.length > 0) {
this.state.urls.forEach((url) => URL.revokeObjectURL(url));
}
this.setState({ urls });
}
displayUploadedFiles(urls) {
return urls.map((url, i) => <img key={i} src={url}/>);
}
Call setFileUrls in your onChange handler, and call displayUploadedFiles in the render method:
render() {
return (
// ... other stuff
{this.state.urls.length > 0 &&
<Fragment>{this.displayUploadedFiles(this.state.urls)}</Fragment>
}
// ... more stuff
);
}
Multiple adjacent elements should be wrapped in a parent element, which can be a div or a React Fragment.
You can explore this npm module React Drop Zone.