Uploading multiple images in React - reactjs

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.

Related

React dropzone clears files

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

React setState hook not updating dependent element if passed a variable as opposed to explicit text

I'm right on the verge of tossing React and just using vanilla JS but thought I'd check here first. I'm simply trying to pass the contents of a variable, which contains an object, into state and have that update the element that depends upon it. If I pass setState a variable containing the object, it doesn't work. If I pass it the explicit text of the object it does.
Using React v 18.0.0
function buttonHandler(e) {
e.preventDefault()
let tmpObject = {...chartData}
tmpObject.datasets[0].data = quoteData.map(entry => entry['3'])
tmpObject.datasets[1].data = quoteData.map(({fastEma}) => fastEma)
tmpObject.datasets[2].data = quoteData.map(({slowEma}) => slowEma)
tmpObject.labels = quoteData.map(entry => new Date(entry.timestamp).toLocaleTimeString())
console.log("from button:", tmpObject)
setChartData(prevState => {
console.log("tmpObject",tmpObject)
return tmpObject
})
return <div>
<button onClick={buttonHandler}>Update</button>
<Line options={chartOptions} data={chartData}/>
</div>
When I run the above, the output of the console.log is exactly as it should be but the element does not update. If I copy the object output from the console and paste it explicitly into the code it does work.
function buttonHandler(e) {
e.preventDefault()
setChartData({...})
I've tried every imaginable variation on the below statement to no avail...
return {...prevState, ...tmpObject}
I'd greatly appreciate any suggestions.
EDIT:
As another test, I added the following HTML element to see if it got updated. It gets updated and shows the expected data. Still, I'm having a hard time understanding why the chart will update if I pass it explicit text but will not if I pass it a variable.
<p>{`${new Date().toLocaleTimeString()} {JSON.stringify(chartData)}`</p>
The issue is that of state mutation. Even though you've shallow copied the chartData state you should keep in mind that this is a copy by reference. Each property is still a reference back into the original chartData object.
function buttonHandler(e) {
e.preventDefault();
let tmpObject = { ...chartData }; // <-- shallow copy ok
tmpObject.datasets[0].data = quoteData.map(entry => entry['3']); // <-- mutation!!
tmpObject.datasets[1].data = quoteData.map(({ fastEma }) => fastEma); // <-- mutation!!
tmpObject.datasets[2].data = quoteData.map(({ slowEma }) => slowEma); // <-- mutation!!
tmpObject.labels = quoteData.map(
entry => new Date(entry.timestamp).toLocaleTimeString()
);
console.log("from button:", tmpObject);
setChartData(prevState => {
console.log("tmpObject",tmpObject);
return tmpObject;
});
}
In React not only does the next state need to be a new object reference, but so does any nested state that is being update.
See Immutable Update Pattern - It's a Redux doc but really explains why using mutable updates is key in React.
function buttonHandler(e) {
e.preventDefault();
setChartData(chartData => {
const newChartData = {
...chartData, // <-- shallow copy previous state
labels: quoteData.map(
entry => new Date(entry.timestamp).toLocaleTimeString()
),
datasets: chartData.datasets.slice(), // <-- new datasets array
};
newChartData.datasets[0] = {
...newChartData.datasets[0], // <-- shallow copy
data: quoteData.map(entry => entry['3']), // <-- then update
};
newChartData.datasets[1] = {
...newChartData.datasets[1], // <-- shallow copy
data: quoteData.map(({ fastEma }) => fastEma), // <-- then update
};
newChartData.datasets[2] = {
newChartData.datasets[2], // <-- shallow copy
data: quoteData.map(({ slowEma }) => slowEma), // <-- then update
};
return newChartData;
});
}
Check your work with an useEffect hook with a dependency on the chartData state:
useEffect(() => {
console.log({ chartData });
}, [chartData]);
If there's still updating issue then check the code of the Line component to see if it's doing any sort of mounting memoization of the passed data prop.

Reading multiple files using FileRead() and adding them on React State

So I'm building an app that allows you to chose more than one photo, on chose I set the files in React State then i listed for change for that state with useEffect so I can iterate and convert to base64 using FileRead to preview what I've uploaded. But I'm having a problem that the data I'm getting is weird, some of the files are read and added to the React State and some just appear just as File, here is the screenshot:
Here is the screenshot of the Console Log (can't add the object because is to long)
And here is how I add to the state the files on upload:
<input
className={styles.hidden_input}
type='file'
multiple='multiple'
accept='image/*'
onChange={(event) => {
const files = event.target.files;
if (files) setImages(files);
else setImages(null);
}}
And here is how I convert them when uploads are made:
useEffect(() => {
if (images.length !== 0) {
for (let i = 0; i < images.length; i++) {
let file = images[i];
const reader = new FileReader();
reader.onload = () => {
const single = reader.result;
setImagesStream([...images, single]);
};
reader.readAsDataURL(file);
}
} else {
console.log('No images where found.');
}
}, [images]);
When I try to iterate, just the last image shows the other show blank, because aren't converted.
You need to pass a function to the setState of 'setImagesStream' so it can read from the current value in the updater instead of reading the current value rendered. Here is the docs reference
Something like this should work:
setImagesStream((state) => {
return [...state, single]
});

How can I prevent React NOT to redownload an image with same url?

Suppose I have such could which may generate several img elements on my page:
const defaultImages = ['A', 'B'];
class App extends React.Component {
state = {
images: [],
}
componentDidMount() {
setInterval(() => {
// I'd like to make react force rebuild the img elements
this.setState({images: []});
this.setState({images: defaultImages});
}, 50);
}
render() {
return (
this.state.images.map((value)=> {
// Everytime when a new img element is created a new http request to the url will be made.
// Can I make React to reuse the already downloaded image, with same url, instead of redownload it again?
return <img key={value} src={`Images/${value}.png`}/>;
})
);
}
The content of my real image list might be changed on condition, such as add or remove some, but the urls would be in a the same set. However, when the image list was rebuilt all the urls will be redonwload thus the img elements get blink which I want to avoid.

Correct way to remove key from react state

I am using the state of a react (v0.14) view to hold key value pairs of unsaved user ids and user objects. For example:
onChange = (user, field) => {
return (event) => {
let newUser = _.clone(this.state[user.uuid] || user);
_.assign(newUser, {[field]: event.target.value});
this.setState({
[user.uuid]: newUser
});
}
}
render() {
let usersJsx = users.map((user, i) => {
return <div key={i}>
<input type="text" value={user.name}
onChange={this.onChange(user, 'name')}/>
</div>;
});
let numberUnsavedUsers = _.keys(this.state).length;
// ... etc
}
This all works perfectly until I come to the save method:
persistUsers = (event) => {
let unsavedUsers = _.toArray(this.state);
updateUsers(unsavedUsers, {
onSuccessCb: (savedUsers) => {
// Would prefer to remove these two lines and replace
// with `this.setState({});` but this doesn't work... i.e.
// the state is left untouched rather than being
// replaced with `{}`. This makes sense. I guess I was hoping
// someone might point me towards a this.replaceState()
// alternative.
this.setState({nothing: true}); // triggers a state change event.
this.state = {}; // wipes out the state.
}
});
}
I've searched around but only found people modifying nested objects or arrays and not top level key values.
You need to use replaceState instead of setState
Update: replaceState is being deprecated. You should follow the recommendation and use setState with null values.
Recommendation:
You should name the data and use setState so you can more easily work with it.
instead of:
//bad
this.setState({
[user.uuid]: newUser
});
use:
//good
this.setState({
newUser: {uuid: user.uuid}
})
If your state was {unsavedUsers: {userData}} instead of {userData} then you could easily setState({unsavedUsers: {}}) without introducing replaceState.
replaceState is an anti-pattern because it is uncommonly used.
Original Answer:
Like setState() but deletes any pre-existing state keys that are not in the newState object.
Documentation
this.replaceState({}) will remove all the objects.

Resources