React.js logging before operation is finished - reactjs

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);
}
}
}

Related

How to solve setState not updating in React?

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.

How to handle array state filter clashes

I am currently having an issue where multiple setStates that use the filtering of an array are interfering with each other. Basically if a user uploads two files, and they complete around the same time, one of the incomplete files may fail to be filtered from the array.
My best guess is that this is happening because they are separately filtering out the one that needs to be filtered, when the second one finishes and goes to filter itself out of the array, it still has the copy of the old incomplete array where the first file has not been filtered out yet. What would be a better way to approach this? Am I missing something obvious? I am thinking of using an object to hold the files instead, but then I would need to create a custom mapping function for the rendering part so that it can still be rendered as if were an array.
fileHandler = (index, event) =>{
let incompleteFiles = this.state.incompleteFiles
incompleteFiles[index].loading = true
incompleteFiles[index].file = event.target.files[0]
this.setState({ incompleteFiles: incompleteFiles },()=>{
const fileData = new FormData()
fileData.append('file', event.targets[0].file)
let incompleteFiles = this.state.incompleteFiles
let completeFiles = this.state.completeFiles
api.uploadFile(fileData)
.then(res=>{
if(res.data.success){
this.setState(state=>{
let completeFile = {
name : res.data.file.name,
}
completeFiles.push(completeFile)
incompleteFiles = incompleteFiles.filter(inc=>inc.label !== res.data.file.name)
return{
completeFiles,
incompleteFiles
}
})
}
})
})
}
Updated with accepted answer with a minor tweak
fileHandler = (index, event) =>{
this.setState(({ incompleteFiles }) => ({
// Update the state in an immutable way.
incompleteFiles: [
...incompleteFiles.slice(0, index),
{
...incompleteFiles[index],
loading: true,
file: event.target.files[0],
},
...incompleteFiles.slice(index+1)
],
}), () => {
const fileData = new FormData()
fileData.append('file', event.targets[0].file)
api.uploadFile(fileData)
.then(res => {
if(res.data.success){
this.setState(({ incompleteFiles, completeFiles }) => ({
completeFiles: [
...completeFiles, // Again, avoiding the .push since it mutates the array.
{ // The new file.
name: res.data.file.name,
}
],
incompleteFiles: incompleteFiles.filter(inc=>inc.label !== res.data.file.name),
})))
}
})
});
}
In class components in React, when setting the state which is derived from the current state, you should always pass a "state updater" function instead of just giving it an object of state to update.
// Bad
this.setState({ counter: this.state.counter + 1 });
// Good
this.setState((currentState) => ({ counter: currentState.counter + 1 }));
This ensures that you are getting the most up-to-date version of the state. The fact that this is needed is a side-effect of how React pools state updates under the hood (which makes it more performant).
I think if you were to re-write your code to make use of this pattern, it would be something like this:
fileHandler = (index, event) =>{
this.setState(({ incompleteFiles }) => ({
// Update the state in an immutable way.
incompleteFiles: {
[index]: {
...incompleteFiles[index],
loading: true,
file: event.target.files[0],
},
},
}), () => {
const fileData = new FormData()
fileData.append('file', event.targets[0].file)
api.uploadFile(fileData)
.then(res => {
if(res.data.success){
this.setState(({ incompleteFiles, completeFiles }) => ({
completeFiles: [
...completeFiles, // Again, avoiding the .push since it mutates the array.
{ // The new file.
name: res.data.file.name,
}
],
incompleteFiles: incompleteFiles.filter(inc=>inc.label !== res.data.file.name),
})))
}
})
});
}
Another thing to keep in mind is to avoid mutating your state objects. Methods like Array.push will mutate the array in-place, which can cause issues and headaches.
I think change code to this can solve your problem and make code easy to read.
fileHandler = async (index, event) =>{
const incompleteFiles = [...this.state.incompleteFiles]
incompleteFiles[index].loading = true
incompleteFiles[index].file = event.target.files[0]
this.setState(
{
incompleteFiles
},
async (prev) => {
const fileData = new FormData()
fileData.append('file', event.targets[0].file)
const res = await api.uploadFile(fileData)
/// set loading state to false
incompleteFiles[index].loading = false
if (!res.data.success) {
return { ...prev, incompleteFiles }
}
// add new file name into completeFiles and remove uploaded file name from incompleteFiles
return {
...prev,
completeFiles: [...prev.completeFiles, { name : res.data.file.name }],
incompleteFiles: incompleteFiles.filter(inc=>inc.label !== res.data.file.name)
}
})
)
}

I cant update my component state.. Do somebody understand how it fix?

I cant understand why my renderMovies() function dont wanna update my component state.data and i cant render component on my screen ?!
Everithing goes ok until renderMovies function.. I think this.setState(newState) in my fetchPostData function is working incorrect... Do somebody know how to fix it? I tried different ways but i cant solve this issue.
class Movies extends React.Component {
constructor(props) {
super(props)
this.state = { data: {}}
this.fetchPostData = this.fetchPostData.bind(this)
this.renderMovies = this.renderMovies.bind(this)
this.populatePageAfterFetch = this.populatePageAfterFetch.bind(this)
}
componentDidMount() {
this.fetchPostData()
}
fetchPostData() {
fetch(`http://localhost/reacttest/wp-json/wp/v2/movies?per_page=100`)
.then(response => response.json())
.then(myJSON => {
let objLength = Object.keys(myJSON).length
let newState = this.state;
for (let i = 0; i < objLength; i++) {
let objKey = Object.values(myJSON)[i].title.rendered;
// console.log(objKey)
let currentMovie = newState.data[objKey];
currentMovie = {};
currentMovie.name = Object.values(myJSON)[i].title.rendered;
currentMovie.description = Object.values(myJSON)[i].content.rendered;
currentMovie.featured_image = Object.values(myJSON)[i]['featured_image_url'];
currentMovie.genre = Object.values(myJSON)[i]['genre'];
}
this.setState(newState)
})
}
renderMovies() {
if(this.state.data) {
const moviesArray = Object.values(this.state.data)
console.log(moviesArray)
return Object.values(moviesArray).map((movie, index) => this.populatePageAfterFetch(movie, index))
}
}
populatePageAfterFetch(movie, index) {
if (this.state.data) {
return (
<div key={index} index={index}>
<h2>{movie.title}</h2>
<h3>{movie.genre}</h3>
<p>{movie.description}</p>
</div>
)
}
}
render() {
return (
<div>
<h1>Movies</h1>
<div>{this.renderMovies()}</div>
</div>
)
}
}
When i try to console.log(moviesArray) it show me:
Issue
You save current state into a variable named newState, never update it, and then save the same object reference back into state. React state never really updates.
let newState = this.state;
for (let i = 0; i < objLength; i++) {
...
}
this.setState(newState);
Additionally you mutate state
let currentMovie = newState.data[objKey];
currentMovie = {};
But this doesn't work either since initial state is an empty object so newState.data[objKey] is aways undefined. (so nothing is ever actually mutated)
Solution
It appears as though you intended to map the myJSON data/values into movie objects to update this.state.data. May I suggest this solution. The key is to always create new object references for any object you update.
fetchPostData() {
fetch(`http://localhost/reacttest/wp-json/wp/v2/movies?per_page=100`)
.then(response => response.json())
.then(myJSON => {
this.setState(prevState => ({
// array::reduce over the JSON values
data: Object.values(myJSON).reduce((movies, movie) => {
// compute movie key
const name = movie.title.rendered;
return {
...movies,
[name]: {
...movies[name], // copy any existing movie properties
// merge in new/updated properties
name,
description: movie.content.rendered,
featured_image: movie.featured_image_url,
genre: movie.genre,
},
}
}, { ...prevState.data }) // use previous state as initial value for reduce
}))
})
}

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

How to stop the state variable to be overwritten from previous value in reactjs?

i am storing the data got from server in a state variable. It works fine if i open items having large data display correct information. however after opening item with no data available for it displays previous item value.
consider the scenario,
item 1 has no data, item2 has large data.
open item1 displays no data for it.
now open item2 displays data for it (which is large)
now open item1 displays item2 data instead of showing no data.
Not sure where i am going wrong.
Below is the code,
class ItemDetails extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
item_info: null,
item_info_loading: false,
};
componentDidMount() {
this.unmount = new Promise((resolve) => { this.on_unmount =
resolve;});
this.load_item_info();
this.unlisten_path_change = this.props.history.listen((location) =>
{this.handle_path_change(location.pathname);});
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.item_id !== this.props.item_id) {
this.setState({item_info: null}, this.load_item_info);}
}
componentWillReceiveProps(nextProps, nextState) {
if(nextProps.item_id !== this.props.item_id) {
this.setState({item_info: null}, this.load_item_info);
}}
componentWillUnmount() {
this.on_unmount();
this.unlisten_path_change();
}
load_item_info = () => {
const file_name = 'item_info.json';
this.setState({item_info_loading: true});
client.get_item_file(this.props.model_id, file_name, 'json',
this.unmount).finally(() => this.setState({item_info_loading: false}))
.then((request) => {
this.setState({item_info: request.response})
})};
render () {
<ItemInfoTool
item_info={state.item_info}
item_info_loading={this.state.item_info_loading}/>}}
export default class ItemInfoTool extends React.Component {
state = {
open_item_data: null,};
componentDidMount() {
this.set_open_item_data();
}
componentDidUpdate(prevProps) {
if (prevProps.selected_id !== this.props.selected_id) {
this.set_open_item_data();
}
}
set_open_item_data = () => {
if (!this.props.item_info) {
return;
}
if (this.props.selected_id === this.empty_id) {
this.setState({open_item_data: null});
return;
}
let open_item_data = {
info: [],
values: [],
};
const item_info = this.props.item_info;
for (let i=0, ii=item_info.length; i < ii; i++) {
if (item_info[i].somekey.includes(this.props.selected_id)) {
const info = item_info[i].info;
const values = object_info[i].values;
open_item_data = {
info: typeof info === 'string' ? info.split('\r\n') : [],
values: values ? values : [],
};
break;
}
}
this.setState({open_item_data:open_item_data);
};}
export function get_item_file(item_id, file_name, response_type,
on_progress, cancel) {
const local_override_defined = item_files[item_id] &&
item_files[item_id][file_name];
if (local_override_defined) {
const file = item_files[item_id][file_name];
const reader = new FileReader();
return new Promise(resolve => {
if (response_type === 'blob') {
resolve({response: file});
} else {
reader.onload = () => {
if (response_type === 'json') {
resolve({response: JSON.parse(reader.result)});
} else {
resolve({response: reader.result});
}};
reader.readAsText(file);}});}
return new Promise((resolve, reject) => {
item_file_get_url(item_id, file_name).then(({response}) => {
const request = new XMLHttpRequest();
request.addEventListener('progress', on_progress);
request.open('GET', response.url);
request.responseType = response_type;
send_request(request, undefined, cancel,
response.url).then(resolve).catch(reject);})});}
Could someone help me solve it. Thanks. i doubt there is some asynchronous requests happening.
You need to clear data while closing operation

Resources