I'm using redux-form#8.1.0 and redux#4.0.1 and saving the data in a MongoDB collection. However, when I watch the file object that I uploaded in my Mongo DBs' collection it just retrieves the name of the file.
FileInput.js is the component that I pass to the redux-form Field component
FileInput.js
import React from 'react';
const handleChange = (handler) => ({target: {files}}) =>
handler(files.length ? {file: files[0], name: files[0].name} : {});
export default ({
input: {onChange, onBlur, value: omitValue, ...inputProps},
meta: omitMeta,
...props
}) => (
<input type="file"
onChange={handleChange(onChange)} onBlur={handleChange(onBlur)}
{...inputProps} {...props} />
);
And this is how I use it in my form
...
import FileInput from './FileInput';
...
<Field name="fileUploaded" component={FileInput} type="file"
/>
And this is the document in the MongoDB collection
{...
"fileUploaded":{"name":"testingfile.png"},
...}
It seems it stores only the name of the file and I expect another key value pair with the file information/object in order to load and display this image/file later.
Redux-Form stores the file, perhaps you need to read it out and then send it as a multipart/form-data. The data should be accessible at state.form.myFormNameHere.values.mFieldNameHere.
I made an ImageDisplay component that may be of help. It reads the file from a red-form file input and displays a preview.
const ImageReader = ({ file }) => {
const reader = new FileReader();
const [imageUrl, setImageUrl] = useState('');
if (file && file.file instanceof Blob) {
reader.onload = (event) => {
const { target: { result } } = event;
setImageUrl(result);
};
reader.readAsDataURL(file.file);
return <Image src={imageUrl} />;
}
return <Image src={null} />;
};
ImageReader.defaultProps = {
file: null,
};
ImageReader.propTypes = {
file: PropTypes.shape({
name: PropTypes.string,
file: PropTypes.any,
}),
};
Related
see the screenshot . Why images getting corrupted? I am trying to upload images using axois post but axois post can't processing my images correctly . My code divided in two part. First part where I writing logic for upload multi image and second part I am using it in my page component.
first part
this code for upload multi image
export const MultiImageUpload = ({Setimage}) => {
const [selectedImages, setSelectedImages] = useState([]);
const onSelectFile = (event) => {
const selectedFiles = event.target.files;
const selectedFilesArray = Array.from(selectedFiles);
const imagesArray = selectedFilesArray.map((file) => {
return URL.createObjectURL(file);
});
setSelectedImages((previousImages) => previousImages.concat(imagesArray));
Setimage((previousImages) => previousImages.concat(imagesArray));
// FOR BUG IN CHROME
event.target.value = "";
};
function deleteHandler(image) {
setSelectedImages(selectedImages.filter((e) => e !== image));
Setimage(selectedImages.filter((e) => e !== image));
URL.revokeObjectURL(image);
}
second part
now I am importing this component in my page
const AdsPost = ({data}) => {
const[image,Setimage] = useState([])
var data = new FormData();
image.forEach(file=>{
data.append("files", file)
console.log("image_url:",file)
})
let submit_ads = axios.post(url,data,{headers:headers}).then((res)=>{
console.log(res)
})
here is myjsx
<MultiImageUpload
Setimage={Setimage}/>
I can upload image using postman but don't know why axois post can't upload images.
Here is your problem :
image.forEach(file=>{
data.append("files", file)
console.log("image_url:",file)
})
The parameter file is not a file, but it's the result of
const imagesArray = selectedFilesArray.map((file) => {
return URL.createObjectURL(file); // <--- this line
});
In other words, you are essentially doing
data.append("files", URL.createObjectURL(file));
Fix that and your code should work.
Solution
Here is a sandbox with a proposed solution, the idea is to delegate the state of the files to a Provider, and use the context down in child components as needed.
./context/files.js
import { createContext } from "react";
export default createContext({
/** #return {{ file:File, dataUrl:string }[]]} */
get files() {
return [];
},
/** #return {Error[]} */
get errors() {
return [];
},
/** #param {File[]} files */
addFiles(files) {},
/** #param {File} file */
removeFile(file) {},
/** #param {Error[]} errors */
setErrors(errors) {}
});
./providers/FilesProvider.jsx
import { useContext, useMemo, useState } from "react";
import FilesContext from "../context/files";
const FilesProvider = ({ children }) => {
const [internal, setInternal] = useState(() => ({
files: [],
errors: []
}));
const contextValue = useMemo(
() => ({
get files() {
return internal.files;
},
get errors() {
return internal.errors;
},
addFiles(filesAdded) {
setInternal(({ files, errors }) => ({
files: files.concat(
filesAdded.map((file) => ({
file,
dataUrl: URL.createObjectURL(file)
}))
),
errors
}));
},
removeFile(fileRemoved) {
URL.revokeObjectURL(fileRemoved);
setInternal(({ files, errors }) => ({
files: files.filter(({ file }) => file !== fileRemoved),
errors
}));
},
setErrors(errors) {
setInternal(({ files }) => ({ files, errors }));
}
}),
[internal]
);
return (
<FilesContext.Provider value={contextValue}>
{children}
</FilesContext.Provider>
);
};
const useFiles = () => useContext(FilesContext);
export default FilesProvider;
export { useFiles };
Usage
<FilesProvider>
<FilesSelectComponent />
</FilesProvider>
and
const { files, errors, addFiles, removeFile, setErrors } = useFiles();
I am preparing a form that has fields to allow the user to post his picture and a video about her/him(self). To post a picture I prepared a component based on input type file, but to upload a video I have used React Dropzone. Now I want to save this to redux, however when I try to do that redux is complaining that it is the non-serializable item, and when I put it into JSON.Stringify() redux is getting an empty object. What would be the optimal solution for that, I have to store it somewhere in-state, should it be a local state created in this step of the form (fortunately it is the last one) however if the user would like to go back to any previous step and come back this data will be lost
Please advise, the dropzone component is below, onResult is just a handler that takes the value and dispatches an action to redux
import React, { useCallback, useState } from "react";
import { useDropzone } from "react-dropzone";
export const FileDropzone = ({ onResult, fileTypes, maxFiles }) => {
const [progress, setProgress] = useState(0);
const [error, setError] = useState(false);
const onDrop = useCallback(acceptedFiles => {
acceptedFiles.forEach(file => {
const reader = new FileReader();
reader.onerror = () => setError(true);
reader.onprogress = data => {
if (data.lengthComputable) {
var progress = parseInt(
(data.loaded / data.total) * 100,
10
);
setProgress(progress);
}
};
reader.onloadend = () => onResult(reader.result);
reader.readAsArrayBuffer(file);
});
}, []);
const { getRootProps, getInputProps } = useDropzone({
onDrop,
maxFiles,
accept: fileTypes
});
return (
<>
<div
className={`border border-${
error ? "danger" : "light"
} rounded d-flex justify-content-center align-items-center hpx-100`}
{...getRootProps()}
>
<input {...getInputProps()} />
<p>Proszę kliknąć, lub upuścić wybrany plik.</p>
</div>
<div
className="bg-primary hpx-20 mt-1"
style={{ width: `${progress}%` }}
></div>
</>
);
};
Thank you
I think your best bet would be to convert the file to Base64.
Please check How to convert file to base64 in JavaScript?
and
https://github.com/reduxjs/redux/issues/2276
I'm trying to create a custom useForm hook which will update an object and handle multiple input types. When I upload an image, I grab the first image and save it to value BUT when I setState with setInputs, the image value in the inputs object is null even though the value is the File object (as I've console logged it before).
I'm not sure if this is a typescript specific error. I tried setting profileImage to be any without any impact.
EDIT: The image is being uploaded correctly, but not displaying when using JSON.stringify as mentioned in the comments... so no actual error.
// Hook usage
const { inputs, handleInput } = useForm<{
name: string
description: string
profileImage: File | null
}>({
name: "",
description: "",
profileImage: null,
})
// Form Input
<input
onChange={handleInput}
type="file"
accept="image/*"
id="profileImage"
name="profileImage"
/>
// useForm hook
export type FormEvent = React.ChangeEvent<HTMLInputElement> | React.ChangeEvent<HTMLTextAreaElement>
export default function useForm<T extends { [key: string]: any }>(initial: T = {} as T) {
const [inputs, setInputs] = useState<T>(initial)
const handleInput = (e: FormEvent) => {
let { value, name, type }: { value: any; name: string; type: string } = e.target
if (type === "number") {
value = parseInt(value)
} else if (type === "file") {
value = (e.target as HTMLInputElement).files![0]
}
setInputs({ ...inputs, [name]: value })
}
return { inputs, setInputs, handleInput }
}
I reproduced a simplified version of your example, just handling the file case. The upload onChange action is handled by the hook, file object is returned and name gets displayed on the HTML.
https://codesandbox.io/s/condescending-mcclintock-qs3c1?file=/src/App.js
function useForm(init) {
const [file, setFile] = React.useState(init);
const handleFileInput = (e) => {
setFile(e.target.files[0]);
};
return {file, handleFileInput};
}
export default function App() {
const {file, handleFileInput } = useForm(null);
console.log('file', file);
return (
<div className="App">
<h3>File Upload Test</h3>
<input type="file" onChange={handleFileInput} />
<div> File Info: {file && file.name} </div>
</div>
);
}
So I am working on my React app and a one point i need to upload some files. So I simply used the input file to make it work. I set its display none as I wanted my attachment icon to be clicked when wanting to upload the file.
Problem : Using the ref method, everything is working fine except one thing and that is below in my hangleFileChange function, when the setFiles() set the file variable, the component is not rendered and I do not see the file array. but if I do the file saving simply like
setFile(event.target.files[0])
I can see the rendering . But with the below code, the component is not rendering
import React, { useRef, useState } from "react";
const App = () => {
const fileInput = useRef(null);
const [file, setFile] = useState([]);
const handleClick = () => {
fileInput.current.click();
};
const handleFileChange = (event) => {
console.log("Make something");
let newFiles = file;
newFiles.push(event.target.files[0]);
console.log(newFiles);
setFile(newFiles);
};
// This should run on every render
console.log("the files array is ", file);
return (
<div className="patientactions-container">
<input
type="file"
style={{ display: "none" }}
onChange={(e) => handleFileChange(e)}
ref={fileInput}
/>
<div onClick={() => handleClick()}>clck</div>
</div>
);
};
export default App;
Please help.
Sandbox : https://codesandbox.io/s/kind-breeze-czc3w?file=/src/App.js:0-692
Try this version
const handleFileChange = (event) => {
console.log("Make something");
// Set the ne variable to an array, not file
let ne = [];
ne.push(event.target.files[0]);
// then set it equals file.
ne = file;
console.log(ne);
console.log(file);
setFile(file);
};
You can fix the code like this below.
import React, { useRef, useState } from "react";
const App = () => {
const fileInput = useRef(null);
const [file, setFile] = useState(null);
const handleClick = () => {
fileInput.current.click();
};
const handleFileChange = (nfile) => {
console.log("Make something");
if (file == null) setFile([nfile]);
else setFile([...file, nfile]);
};
console.log("the files array", file);
return (
<div className="patientactions-container">
<input
type="file"
style={{ display: "none" }}
onChange={(e) => handleFileChange(e.target.files[0])}
ref={fileInput}
/>
<div onClick={() => handleClick()}>clck</div>
</div>
);
};
export default App;
I'd have put this in the comments but my rep is not high enough.
I had a problem with rendering changes to an array, because arrays use pointers it did not "register" a state change that was enough to cause a render. Using the spread operator in your solution affected the pointer and thus a render occurred.
In my own solution I set my array to null before adding content and that worked fine for my problem.
i am currently trying to integrate multiple filepond components/instances into my react app, where the images are uploaded on button/form submit.
I have it more or less working with the code below, but i'm getting an undefined result when logging out the results in routes.js file, even though when I log out the state on submit in upload.js, i do get results.
I've tried logging out just req.files, it returns undefined, the method i've used below comes directly from multers documentation and this logs out - TypeError: Cannot read property '0' of undefined
Thanks
upload.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import axios from "axios";
import "./styles.css";
import { FilePond, registerPlugin } from "react-filepond";
import "filepond/dist/filepond.min.css";
// import FilePondPluginFileEncode from 'filepond-plugin-file-encode';
import FilePondPluginImageExifOrientation from "filepond-plugin-image-exif-orientation";
import FilePondPluginImagePreview from "filepond-plugin-image-preview";
import "filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css";
// Register the plugins
registerPlugin(FilePondPluginImageExifOrientation, FilePondPluginImagePreview);
const API_BASE = "http://localhost:5000";
function submitForm(contentType, data, setResponse) {
axios({
url: `${API_BASE}/upload`,
method: "POST",
data: data,
headers: {
"Content-Type": contentType
}
})
.then(response => {
setResponse(response.data);
})
.catch(error => {
setResponse("error");
});
}
function App() {
const [title, setTitle] = useState("");
const [file, setFile] = useState("");
const [file3, setFile3] = useState("");
const [desc, setDesc] = useState("");
function uploadWithFormData() {
const formData = new FormData();
formData.append("title", title);
formData.append("file", file);
formData.append("file3", file3);
formData.append("desc", desc);
submitForm("multipart/form-data", formData, msg => console.log(msg));
}
return (
<div className="App">
<h2>Upload Form</h2>
<form>
<label>
File Title
<input
type="text"
vaue={title}
onChange={e => {
setTitle(e.target.value);
}}
placeholder="Give a title to your upload"
/>
</label>
<FilePond
name={file}
files={file}
allowMultiple={false}
server={null}
instantUpload={false}
onupdatefiles={setFile}
/>
<FilePond
name={file3}
files={file3}
allowMultiple={false}
server={null}
instantUpload={false}
onupdatefiles={setFile3}
/>
<label>
Description
<textarea value={desc} onChange={e => setDesc(e.target.value)} />
</label>
<input
type="button"
value="Upload as Form"
onClick={uploadWithFormData}
/>
</form>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
routes.js
router.post('/', upload.fields([{ name: 'file'}, { name: 'file3' }]), (req, res) => {
console.log(req.files['file'][0]);
console.log(req.files['file3'][0]);
var movieData = {
desc: req.body.desc,
title: req.body.title,
imgCollection: req.files['file'],
poster: req.files['file3']
};
Movie.create(movieData)
.then(movie => res.json({ msg: 'Movie added successfully' }))
.catch(err => res.status(400).json({ error: 'Unable to add this movie' }));
});
image of console log
console log upload.js
What you're loggin is the FilePond file item, it's not a File object. The file item has a file property that contains the actual file object, if the array you logged is what you're posting to the server you need to map it so it contains file objects instead of FilePond file items.
You should not pass any Content-type in headers.
Get file property from the files while appending like below,
formData.append("file", files.file);