How to solve setState not updating in React? - reactjs

Here I'm trying to select multiple files and upload them at one click.When I select single file and upload then it render correctly. But when I upload more than one file ,the last one is not displaying.But it gets stored in the array.It's like always on step behind.Below 2 function
handleClick = (fileUploader) => {
fileUploader.current.click();
// this.setState({ selectedFile: event.target.files[0] });
};
handleChangeFile = (event) => {
for (var i = 0; i < event.target.files.length; i++) {
const fileUploaded = event.target.files[i];
this.setState({ uploadedFileName: fileUploaded.name });
this.setState({ uploadedFileType: fileUploaded.type });
this.setState({
selectedFiles: this.state.selectedFiles.concat(fileUploaded)
});
}
console.log(this.state.selectedFiles);
};
Inside render
const decription = this.state.selectedFiles.map((item) => {item.name}

I found a way to solve this.
handleChangeFile = (event) => {
var temp = [];
for (var i = 0; i < event.target.files.length; i++) {
temp.push(event.target.files[i]);
}
this.setState({
selectedFiles: this.state.selectedFiles.concat(temp)
});
};
We must create a temp array. This is easy.

Related

React.js logging before operation is finished

onReaderLoad = (e) => {
var obj =JSON.parse(e.target.result)
this.setState(prevState => ({
datasource: [...prevState.datasource, obj]
}))
}
ReadFiles = () => {
let files = this.state.json_files;
for (let i of files){
var reader = new FileReader();
reader.onload = this.onReaderLoad;
reader.readAsText(i);
}
console.log(this.state.datasource)
}
getfolder = (e) => {
var files = e.target.files;
this.setState({
json_files: files
}, () => this.ReadFiles())
}
<input type="file" onChange={this.getfolder} multiple accept=".json" />
Here i am sharing my code.
What i am trying to do is i am reading all the json files from user input and looping them and storing it to react state.
Then inside ReadFiles() function i am logging the state data. But it is always coming empty data.
I think it calling first and then going to the loop.
I wants to log the datasource data from state inside ReadFiles() function after all Looping operation is done
Is there any way to do that ?
Please have a look
You can make use of instance variable in this case to keep track of whether you are processing last file or not. Once you know that last file is being processed, you can set doNextStep to true in state from callback of last processed file. Then in componentDidUpdate, you can do next step(i.e. console log or anything else).
class Example extends React.Component {
constructor(props){
super(props);
// to keep track of if last file is being processed
this.lastFile = false;
// doNextStep will tell us that all setState operations are completed or not
this.state = {
doNextStep: false,
}
}
componentDidUpdate(prevProps, prevState) {
if(prevState.doNextStep !== this.state.doNextStep && this.state.doNextStep) {
console.log(this.state.datasource)
}
}
onReaderLoad = (e) => {
var obj =JSON.parse(e.target.result)
this.setState(prevState => ({
datasource: [...prevState.datasource, obj]
}),
() => if(this.lastFile)) this.setState({doNextStep: true}))
}
ReadFiles = () => {
let files = this.state.json_files;
for (i=0; i<files.length; i++){
if(i == files.length - 1) this.lastFile = true
else this.lastFile = false
var reader = new FileReader();
reader.onload = this.onReaderLoad;
reader.readAsText(i);
}
}
}

React Ant Design multiple files upload doesn't work

I'm in the process of sending multiple files from "React.js" by formData.append() to a backend.
At the backend(Spring boot), I was able to see that multiple files were saved well with postman.
The problem occurred in React.
(I'm using "Ant Design" that is React UI Library.)
Below is the source that append files to formdata with extra data.
const formData = new FormData();
formData.append('webtoonId', this.state.selectedToonId);
formData.append('epiTitle', this.state.epiTitle);
formData.append('eFile', this.state.thumbnail[0].originFileObj);
for( let i = 0; i< this.state.mains.length ; i++){
formData.append('mFiles', this.state.mains[i].originFileObj);
}
uploadEpi(formData)
uploadEpi() is POST API.
Below is about state.
this.state = {
toons: [],
epiTitle :'',
thumbnail : [],
mains : [],
selectedToonID : ''
}
When I submit, Text and single file are stored in the DB normally, but only multiple files cannot be saved.
There was no error. Just multiple files didn't be saved.
The state "mains" is configured as shown below.
I guess it's because I'm using Ant Design incorrectly.
(Ant Design : https://ant.design/components/upload/)
Why I guessed so, because when I add multiple attribute to <Dragger> like below,
<Dragger onChange={this.onChangeMain} beforeUpload={() => false} multiple={true}>
the state "mains" multiple files became undefined.
Below is onChange={this.onChangeMain}
onChangeMain=({ fileList })=> {
this.setState({ mains : fileList }, function(){
console.log(this.state)
});
}
The bottom line is, I want to know how to upload multiple files through <Upload> (or <Dragger>) in "React Ant Design."
I don't know what should I do.
this is my github about this project.
I'd appreciate with your help. thx.
const [loading, setLoading] = useState<boolean>(false);
const [fileList, setFileList] = useState<any[]>([]);
const [/* fileListBase64 */, setFileListBase64] = useState<any[]>([]);
const propsUpload = {
onRemove: (file:any) => {
const index = fileList.indexOf(file);
const newFileList:any = fileList.slice();
newFileList.splice(index, 1);
return setFileList(newFileList)
},
beforeUpload: (file:any) => {
setFileList([...fileList, file]);
return false;
},
onChange(info:any) {
setLoading(true);
const listFiles = info.fileList;
setFileList(listFiles);
const newArrayFiles = listFiles.map((file:any) => file.originFileObj? (file.originFileObj) : file );
const anAsyncFunction = async (item:any) => {
return convertBase64(item)
}
const getData = async () => {
return Promise.all(newArrayFiles.map((item:any) => anAsyncFunction(item)))
}
getData().then(data => {
/* setFileSend(data) */
setFileListBase64(data);
setLoading(false);
// console.log(data);
});
},
directory: true,
fileList: fileList,
};
const convertBase64 = (file:File) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.readAsDataURL(file)
fileReader.onload = () => {
resolve(fileReader?.result);
}
fileReader.onerror = (error) => {
reject(error);
}
})
}
const handleDeleteListFiles = () => {
setFileList([]);
setFileListBase64([]);
}
It seems like you are overriding the value of mFiles.
const formData = new FormData();
formData.append('webtoonId', this.state.selectedToonId);
formData.append('epiTitle', this.state.epiTitle);
formData.append('eFile', this.state.thumbnail[0].originFileObj);
let mFiles = [];
for( let i = 0; i< this.state.mains.length ; i++){
mFiles[i] = this.state.mains[i].originFileObj;
}
formData.append('mFiles', mFiles)
uploadEpi(formData)
Maybe this can work: formData.append('mFiles[]', mFiles)
If you add [] to the string it should not overwrite but add to the array

this.setState is not pushing items into an array when using a for loop in React

How can I transfer individual items from one array into another using react?
I have an array of items in state I created, but I need to send them one by one into another state. What's the best way to do this? I tried callback functions and I'd rather not use setTimeone.
state = { imageChosen: [] };
// Choose multiple images to upload
fileSelectedHandler = event => {
let tmp = [];
for (let i = 0; i < event.target.files.length; i++) {
tmp.push(event.target.files[i])
}
this.setState({ imageChosen: tmp }, () => this.uploadMedia());
}
// Add chosen images to the media chosen array
uploadMedia = () => {
for (let i = 0; i < this.state.imageChosen.length; i++) {
this.setState({
mediaChosenArr: [...this.state.mediaChosenArr, this.state.imageChosen[i]],
displayMediaChosenArr: [...this.state.displayMediaChosenArr, URL.createObjectURL(this.state.imageChosen[i])],
imageChosen: [],
});
}
document.getElementById("mediaPlaceholder").value = '';
};
using setState() with for-loop is not recomendable.
EDITED
fileSelectedHandler = event => {
let mediaChosenArr = [];
let displayMediaChosenArr = [];
for (let i = 0; i < event.target.files.length; i++) {
mediaChosenArr.push(event.target.files[i]);
displayMediaChosenArr.push(URL.createObjectURL(event.target.files[i]);
});
}
this.setState({
mediaChosenArr: mediaChosenArr,
displayMediaChosenArr: displayMediaChosenArr
});
document.getElementById("mediaPlaceholder").value = ''; // It seems not good.
}
Avoid calling setState from inside a loop as each call to setState will result in a render.
Instead assemble the imageChosen array first, and then pass it to setState. Also since you're not modifying the files array you should just be able to pass it directly to setState without looping through it.
Also keep in mind state updates may be asynchronous
fileSelectedHandler = event => {
const files = event.target.files;
this.setState(
}
You can using template array.
state = { imageChosen: [] };
fileSelectedHandler = event => {
let tmpArray = [...imageChosen];
for (let i = 0; i < event.target.files.length; i++) {
tmpArray.push(event.target.files[i]);
console.log(event.target.files[i])
}
this.setState({imageChosen: tmpArray})
}
You can do this several ways, here is one with forEach.
const initialArray = ['one.png','two.png','three.png']
this.state = { imageChosen: [] };
let {imageChosen} = this.state
initialArray.forEach(insertImages);
function insertImages(value) {
this.setState({imageChosen: value})
}

Firebase upload multiple files and get status

I have a React form where the user can upload multiple files. These are stored in fileList
async function uploadFiles(id) {
try {
const meta = await storageUploadFile(fileList, id);
console.log(meta);
} catch (e) {
console.log(e);
}
}
This calls my helper function that uploads the files to Firebase
export const storageUploadFile = function(files, id) {
const user = firebase.auth().currentUser.uid;
return Promise.all(
files.map((file) => {
return storage.child(`designs/${user}/${id}/${file.name}`).put(file)
})
)
};
What I'd like is on calling uploadFiles, get the total filesize of all items, and then show the overall progress.
At the moment, my code is only returning the file status in an array on completion
[
{bytesTransferred: 485561, totalBytes: 485561, state: "success"},
{bytesTransferred: 656289, totalBytes: 656289, state: "success"}
]
This is the way i do it:
import Deferred from 'es6-deferred';
export const storageUploadFile = function(files, id) {
const user = firebase.auth().currentUser.uid;
// To track the remaining files
let itemsCount = files.length;
// To store our files refs
const thumbRef = [];
// Our main tasks
const tumbUploadTask = [];
// This will store our primses
const thumbCompleter = [];
for (let i = 0; i < files.length; i += 1) {
thumbRef[i] = storage.ref(`designs/${user}/${id}/${file.name}`);
tumbUploadTask[i] = thumbRef[i].put(files[i]);
thumbCompleter[i] = new Deferred();
tumbUploadTask[i].on('state_changed',
(snap) => {
// Here you can check the progress
console.log(i, (snap.bytesTransferred / snap.totalBytes) * 100);
},
(error) => {
thumbCompleter[i].reject(error);
}, () => {
const url = tumbUploadTask[i].snapshot.metadata.downloadURLs[0];
itemsCount -= 1;
console.log(`Items left: ${itemsCount}`)
thumbCompleter[i].resolve(url);
});
}
return Promise.all(thumbCompleter).then((urls) => {
// Here we can see our files urls
console.log(urls);
});
};
Hope it helps.

Adazzle Grid OnEdit change cell Css

For react, is it possible to change the cell color for a component? I know it is way super easy can be done in Jquery by looking at the id/class of the element and do an addClass, but how to do it on react? I'm still figuring it out the changes, what i can think of is that i set the value on a state, but how i get the affected cell and implement the state? Any idea? Or is there any better grid that I can achieve like what can be done in Jquery?
constructor(props, context) {
super(props, context);
this.state = {
cellUpdateCss: ""
};
}
handleGridRowsUpdated = ({ cellKey, fromRow, toRow, updated, action, originRow }) => {
let rows = this.state.rows.slice();
for (let i = fromRow; i <= toRow; i++) {
let rowToUpdate = rows[i];
let updatedRow = update(rowToUpdate, { $merge: updated });
rows[i] = updatedRow;
this.postToServer(updatedRow.data, JSON.stringify(updated));
}
this.setState({ rows });
};
postToServer = (aX, vC) => {
var self = this;
axios.post(this.props.postApi, {
axNo: aX,
updated: vC
})
.then((response) => {
console.log(response);
self.setState({ cellUpdateCss: "green" });
})
.catch((error) => {
console.log(error);
self.setState({ cellUpdateCss: "red" });
});
}
render() {
return (
<ReactDataGrid
onGridRowsUpdated={this.handleGridRowsUpdated}
/>
)
}
third party grid that I use
https://github.com/adazzle/react-data-grid
You can accomplish that by applying the style to the row class name, first of all, in handleGridRowsUpdated function you need to set fromRow, toRow to the constructor state then we need to call the changeStyle function that I did with the callback of setState.
handleGridRowsUpdated = ({ cellKey, fromRow, toRow, updated, action, originRow }) => {
let rows = this.state.rows.slice();
for (let i = fromRow; i <= toRow; i++) {
let rowToUpdate = rows[i];
let updatedRow = update(rowToUpdate, { $merge: updated });
rows[i] = updatedRow;
this.postToServer(updatedRow.data, JSON.stringify(updated));
}
this.setState({ rows, fromRow: fromRow, toRow: toRow },()=>{
this.changeStyle();
});
};
changeStyle function, it will change the color of the edited row.
changeStyle = () => {
var all = document.getElementsByClassName('react-grid-Row');
for (var i = this.state.fromRow; i <= this.state.toRow; i++) {
all[i].style.color = this.state.cellUpdateCss;
}
}
I did a full example on CodeSandbox

Resources