Add multiple files with onChange function and React Hooks, but singly - javascript

I need to add to files with react components. Here how I'm doing it with one file(onChange and onSubmit functions):
const onChangeFile = e => {
setFileData(e.target.files[0]);
};
const onSubmit = e => {
e.preventDefault();
const newItem = new FormData();
newItem.append('item', fileData);
const { description } = offerData;
newItem.append('description', description);
addItem(newItem);
setFileData(null);
}
Input(reactstrap):
<CustomInput
type="file"
name="item" id="item" label="Choose item image..."
onChange={onChangeFile}
/>
And here, how I'm doing it with multiple files, but with one input:
const onChangeFile = e => {
setFileData(e.target.files);
};
const onSubmit = e => {
e.preventDefault();
const newItem = new FormData();
for (const key of Object.keys(fileData)) {
newItem.append('item', fileData[key])
}
const { description1, description2 } = item;
newItem.append('description1', description1);
newItem.append('description2', description2);
addItem(newItem);
setFileData(null);
}
and input:
<CustomInput
type="file"
name="item"
id="item"
multiple
label="Add images/image..."
onChange={onChangeFile}
/>
And both works, but this time I want to add multiple files( two exactly), with two single inputs and my useState hook doesn't work(like that it isn't iterable). Here's how it looks like for both ways.
const [fileData, setFileData] = useState(null);
So, how to add one object with two images, but added with two single inputs?

Not sure if I fully understand. So the way you have it now you have a single input to receive data, however you want to be able to update your state from two inputs?
When you are setting state you are doing:
const onChangeFile = e => {
setFileData(e.target.files);
};
If this same handler is hooked up to another input, the second set of files will just override the first.
If you want to keep adding to your state you could either use an object, or an array. So onChange could look something like this:
const onChangeFile = e => {
// assuming here that e.target.files is an array already
const filesToAdd = e.target.files;
setFileData([...filesData, ...filesToAdd]);
};
Or with an object
const onChangeFile = e => {
const filesToAdd = e.target.files.reduce(
(map, file) => ({..., [file.name]: file}), {}));
const filesDataUpdate = {
...filesData,
...filesToAdd
}
setFileData(filesDataUpdate);
};
In this case I am assuming each file has a unique name. You could key it with any unique value.
Hope that helps!

Related

Remove all the elements of the array in React JS

I have written a function which is triggered by a dropdown list's onChange event.
Once the function is triggered, it load data from rest API according to the parameter value. However, the array which I have used to store those data is not cleared in the subsequent dropdown change events. As a result, when the user change the dropdown multiple times, the array grows with its previously loaded data.
for example:
In first drop down change => ['A','B','C']
2nd drop down change => ['A','B','C','E','F','G'] instead of ['E','F','G']
My code is as follows:
onDropdownChange = (e) => {
var newArray = [];
// To remove all the elements of newArray
newArray.forEach((e1,idx, arr) => {
newArray.splice(idx,1);
});
console.log(newArray);
const url = 'https://abcdefgh...../' + e + '/readings?today';
newArray = this.state.data.slice();
axios.get(url).then(res => {
var response_arr = res.data.items;
res.data.items.forEach((item, index) => {
newArray.push({ time: res.data.items[index].dateTime, TM: res.data.items[index].value })
})
let sortedArray = newArray.sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime());
this.setState({ data: sortedArray });
this.fetchChart();
});
}
fetchChart = () => {
const { data } = this.state;
return <CustomLineChart data={data} />
}
render(){
const {items} = this.state;
var data = [];
const itemList = items.map(item => {
return <tr key={item.dateTime+''+item.value}>
<td style={{whiteSpace: 'nowrap'}}>{item.dateTime}</td>
<td>{item.measure}</td>
<td>{item.value}</td>
</tr>
});
return (
<div>
{this.fetchChart()}
<CustomDropdownList changeLink={this.onDropdownChange.bind(this)} />
</div>
);
}
}
export default Dashboard;
Can anybody assist me to resolve this issue?
You are copying the previous data with
newArray = this.state.data.slice();
You do not need that.. In fact you do not need any of this if you want to create a new array with new options.
You're adding the results to newArray which starts out empty that you tried to clear, then it's set to the existing data with additional data added. Also, avoid using var.
var newArray = [];
// This is already empty
newArray.forEach((e1,idx, arr) => {
newArray.splice(idx,1);
});
// This will always be empty
console.log(newArray);
const url = 'https://abcdefgh...../' + e + '/readings?today';
// Existing data, remove this
newArray = this.state.data.slice();
axios.get(url).then(res => {
var response_arr = res.data.items;
res.data.items.forEach((item, index) => {
// This is where new data is being added to existing
newArray.push({ time: res.data.items[index].dateTime, TM: res.data.items[index].value })
})

onclick of my submit button, the array stored in localstorage does not add the new item but is replaced while using react

I'm trying to add data from a single input field to an array which i want to store in localstorage but when i submit input button, the item is stored at first but if i try to store a second item, the array previous item is replaced with the newly typed item data instead of adding to it like i'd expect an array to behave. i don't understand this behaviour. i will really really appreciate a detailed explanation since i'm using react to do this.
This is my code below
input field
import React from "react";
import "./addoption.css";
function AddOption({ validateOption }) {
const handleAddoption = (e) => {
e.preventDefault();
const inputValue = e.target.elements[0].value.trim();
validateOption(inputValue);
e.target.elements[0].value = "";
};
return (
<div className="addoption">
<form onSubmit={handleAddoption}>
<input type="text" name="list" />
<button>Add Option</button>
</form>
</div>
);
}
export default AddOption;
*this is my code to add the input data to the localstorage *
const handleAddoption = (option) => {
if (!option) {
return setErrorhandler("Enter valid value to add item");
} else if (listItems.options.indexOf(option) > -1) {
return setErrorhandler("This option already exists!");
}
const array = localStorage.getItem("Options");
let items = [];
if (array) {
items = JSON.parse(array);
}
let storedArray = JSON.stringify(items.push(option));
localStorage.setItem("options", storedArray);
setListItems({ options: items });
};
``
Array.prototype.push certainly mutates the array, but it's return value isn't the array, it's the new length of the array. You may want to do the mutation separate from the JSON serializing.
The reason for the overwriting is because you're using two different storage keys for getting and setting. You are not getting what was stored so you are only appending new data to an empty array. Make sure you also use the same key to both retrieve and set the localStorage.
const handleAddoption = (option) => {
if (!option) {
return setErrorhandler("Enter valid value to add item");
} else if (listItems.options.indexOf(option) > -1) {
return setErrorhandler("This option already exists!");
}
const array = localStorage.getItem("options");
let items = [];
if (array) {
items = JSON.parse(array);
}
items.push(option);
localStorage.setItem("options", JSON.stringify(items));
setListItems({ options: items });
};
A more optimal solution would be to read in and initialize the options state from localStorage, and use an useEffect hook to just persist state updates back to localStorage. This way is a little easier to manage.
Example:
const initializeState = () => ({
// ... other listItems initial state
options: JSON.parse(localStorage.getItem("options")) || [],
});
const [listItems, setListItems] = useState(initializeState());
useEffect(() => {
localStorage.setItem("options", JSON.stringify(listItems.options));
}, [listItems.options]);
const handleAddoption = (option) => {
if (!option) {
return setErrorhandler("Enter valid value to add item");
} else if (listItems.options.indexOf(option) > -1) {
return setErrorhandler("This option already exists!");
}
setListItems(prevState => ({
...prevState
options: prevState.options.concat(option),
}));
};

React upload multiple files at once

I have the following code for uploading multiple images in my React app. The problem is that console.log(e) prints Progress Event object with all its values, but when I want to update my state I still be the default values, null, 0, []. I understand that onload is asynchronous and that might be the reason they are not updated. Technically the code is working when I upload file one by one. When I select multiple files at once, only the first one is being displayed. What am I doing wrong here?
const [fileUpload, setFileUpload] = useState(null);
const [filesUploaded, setFilesUploaded] = useState([]);
const [filesUploadedCount, setFilesUploadedCount] = useState(0);
const handleFileUpload = (e) => {
if (filesUploadedCount === 5 || e.currentTarget.files > 5) {
return;
}
const files = e.currentTarget.files;
console.log(files.length);
console.log(e.currentTarget.files);
Array.from(files).forEach((file: any) => {
const reader = new FileReader();
reader.onload = (e) => {
console.log(e); // Progress Event {}
setFileUpload(e.target.result);
setFilesUploadedCount(filesUploaded.length + 1);
setFilesUploaded([...filesUploaded, e.target.result]);
console.log(fileUpload); // null
console.log(filesUploaded); // []
console.log(filesUploaded.length); // 0
console.log(filesUploadedCount); // 0
};
reader.readAsDataURL(file);
});
};
Here I display them.
{filesUploaded?.map((file, index) => {
return (
<ItemImage
key={index}
src={file}
handleRemoveFile={handleRemoveFile}
/>
);
})}
useState is also asynchronous operation, so you should not rely on their values for calculating the next state. Pass a function like this. You may not see in your console.log because of that.
setFilesUploaded(prevState => [...prevState, e.target.result]);

React one state data automatically changing

handleSearch = (ev, index) => {
const { dropdown_datas, dropdown_datas_search } = this.state;
const keys = Object.keys(dropdown_datas_search);
const current_key = keys[index];
let value = ev.target.value;
let current_dropdown_data = dropdown_datas_search[current_key];
const filtered_data = current_dropdown_data.filter(robots => {
return robots.label.toLowerCase().includes(value.toLowerCase());
})
dropdown_datas[current_key] = filtered_data
this.setState({
dropdown_datas: dropdown_datas
})
}
I am using this function in react.
"dropdown_datas_search" data is coming from state which data i don't wants change.
But, when i am doing the filter "dropdown_datas_search" is also changing from state.
I wants to only change and filter "current_dropdown_data" data .
Please take a look.
How can i prevent doing this.

Too many re-renders in React

The program should take the input user typed, search the data and return results in a drop down list.
When the userinput is more than 3 symbols, the Search() is called and I get "Error: Too many re-renders". Can't find where is the render loop.
import LTCityNames from "../lt-city-names.json"; //JSON object
const Openweathermap = () => {
const [searchList, setSearcList] = useState([]); //drop down list according to search word
const [text, setText] = useState(""); //text in the input field
const Search = (userinput) => {
let correctResult = "";
let dropdownList = [];
const regex = new RegExp(`^${userinput}`, "i");
for (let i = 0; i < LTCityNames.length; i++) {
correctResult = regex.test(LTCityNames[i].name);
if (correctResult){
dropdownList.push(LTCityNames[i]);
setSearcList(dropdownList);
}
}
};
const onChangeInput = (userinput) => {
setText(userinput);
if (userinput.length > 2) {
Search(userinput);
}
};
return (
<input
value={text}
onChange={(e) => {onChangeInput(e.target.value)} }
type="text"
placeholder="Enter address"
></input>
<div id="myDropdownWeather" className="dropdown-content">
{searchList.map((itemInArray) => {
return (
<ul>
<li>{itemInArray.name}</li>
</ul>
);
})
}
I think you must use useEffect like this:
const [text, setText] = useState(""); //text in the input field
const lastFilter = useRef(text);
useEffect(() => {
if (lastFilter.current !== text && text.lenght>2) {
Search(userinput);
lastFilter.current = text;
}
}, [text]);
const onChangeInput = (event) => {
var userinput=event.target.value;
setText(userinput);
};
and change
onChange={(e) => {onChangeInput(e.target.value)} }
to
onChange={(e) => {onChangeInput(e)} }
First: Why you are getting "Error: Too many re-renders"?
When you are using React Functional Components, every time you call a "setState" React reload all your Component, and since you are using functions inside you component these functions are also being loaded every single time your component change. So, when you type your search, the element will re-render uncontrollably.
Solving the problem:
Every time you want to use a function inside a React Functional Component you must use React.useCallback because this way you can control exactly when a function should be reloaded in memory preventing the errors you are getting.
One more thing, inside your return when you are working with react you cannot return more than one JSX Element, this will also cause you a lot of problems, to solve this you can use the fragment element <> ... </> or any other master element that will hold all the others (fragment elements will not interfere with you CSS).
The Code:
import React, { useCallback, useState } from 'react';
import LTCityNames from '../lt-city-names.json'; // JSON object
const Openweathermap = () => {
const [searchList, setSearcList] = useState([]); // drop down list according to search word
const [text, setText] = useState(''); // text in the input field
const Search = useCallback((userinput) => {
const correctResult = '';
const dropdownList = [];
const regex = new RegExp(`^${userinput}`, 'i');
for (let i = 0; i < LTCityNames.length; i++) {
const correctResult = regex.test(LTCityNames[i].name);
if (correctResult) {
dropdownList.push(LTCityNames[i]);
setSearcList(dropdownList);
}
}
}, []);
const onChangeInput = useCallback(
(e) => {
const userinput = e.target.value;
setText(userinput);
if (userinput.length > 2) {
Search(userinput);
}
},
[Search],
);
return (
<> // Fragment element start
<input
value={text}
onChange={(e) => onChangeInput(e)}
type="text"
placeholder="Enter address"
/>
<div id="myDropdownWeather" className="dropdown-content">
{searchList.map((itemInArray) => {
return (
<ul>
<li>{itemInArray.name}</li>
</ul>
);
})}
</div>
</> // Fragment element end
);
};
Understanding useCallback:
useCallback is a React function that will receive 2 parameters the first one is your function and the second one is an array of parameters that when changed will trigger a reload in memory for the function (every time you use an element that came from outside the function itself you need to use it as a parameter to reload the function in memory).
const myReactFunction = useCallback(() => {}, [a,b,c....] )
Improving you Component Return:
You are not required to use any of the tips listed bellow but they will improve the readability of your code.
Since you are calling your input onChange with (e) => onChangeInput(e) you can change your input to only onChangeInput:
<input
value={text}
onChange={onChangeInput} // same as (e) => function(e)
type="text"
placeholder="Enter address"
/>
The second tip is inside you map function, since you are using arrow functions you are not required to type return():
{searchList.map((itemInArray) => (
<ul>
<li>{itemInArray.name}</li>
</ul>
))}
import LTCityNames from "../lt-city-names.json"; //JSON object
const Openweathermap = () => {
const [searchList, setSearcList] = useState([]); //drop down list according to search word
const [text, setText] = useState(""); //text in the input field
const Search = (userinput) => {
let correctResult = "";
let dropdownList = [];
const regex = new RegExp(`^${userinput}`, "i");
for (let i = 0; i < LTCityNames.length; i++) {
correctResult = regex.test(LTCityNames[i].name);
if (correctResult){
dropdownList.push(LTCityNames[i]);
setSearcList(dropdownList);
}
}
};
const onChangeInput = (userinput) => {
setText(userinput);
if (userinput.length > 2) {
Search(userinput);
}
};
//remove value={text}
return (
<input
onChange={(e) => {onChangeInput(e.target.value)} }
type="text"
placeholder="Enter address"
></input>
<div id="myDropdownWeather" className="dropdown-content">
{searchList.map((itemInArray) => {
return (
<ul>
<li>{itemInArray.name}</li>
</ul>
);
})
}
Remove value = {text}

Categories