useEffect/componentWillUnmount fires but state is empty - reactjs

I have a hook component that allows a user to upload a set of images. I want to set it up in such a way that when the component is un mounted that all the files are uploaded to my backend.
currently using useEffect with a return of a function as the componentWillUnmount substitute, however when the function is called the state that it requires (the set of files uploaded) is empty i.e. empty list. Is there a way to fix this or a better way to do it? I suspect its because the useState for the stagedUploadedImages is set to an empty list. Its not an option to lift the state out of this component.
const [stagedUploadedImages, setStagedUploadedImages] = useState([]);
const uploadStagedFiles = () => {
// when this is reached by the useEffect method `stagedUPloadedImages` is empty list
stagedUploadedImages.forEach((file) => {
const formData = new FormData();
formData.append("files", file);
api.uploads.uploadWithNoAssociation(formData).then((response) => {
console.log("ImageGallery: response: ", response);
});
});
};
useEffect(() => {
return () => {
uploadStagedFiles();
};
}, []);
const handleUpload = (files) => {
setStagedUploadedImages([...files]);
};

Explanation: https://www.timveletta.com/blog/2020-07-14-accessing-react-state-in-your-component-cleanup-with-hooks/
const [stagedUploadedImages, setStagedUploadedImages] = useState([]);
const valueRef = useRef();
const uploadStagedFiles = () => {
valueRef.current.forEach((file) => {
const formData = new FormData();
formData.append("files", file);
api.uploads.uploadWithNoAssociation(formData).then((response) => {
console.log("ImageGallery: response: ", response);
});
});
};
useEffect(() => {
valueRef.current = stagedUploadedImages;
}, [stagedUploadedImages]);
useEffect(() => {
return () => {
uploadStagedFiles();
};
}, []);
Additional info: https://dmitripavlutin.com/react-hooks-stale-closures/

Related

How to prevent object undefined in React

I have react app requeting a flask server, I can return the objects but when I assing the state to a new variable it log undefined, even though I am able to log it
const [characters, setCharacters] = useState([]);
useEffect(() => {
const getData = async () => {
await fetch("http://127.0.0.1:6789/api/load_img_data")
.then((res) => res.json())
.then(
(result) => {
const arrayOfObj = Object.entries(result.imgData).map((e) => e[1]);
setCharacters(arrayOfObj);
},
(error) => {}
);
};
getData();
}, []);
console.log(characters); ## it works fine and log the object on the console
const columnsFromBackend = {
["1"]: {
name: "Terminator Group",
items: characters, ## doesn't work when I map over it as it will be always empty
}
}
so my question what it the best way to assign a state to variable? thanks
You can declare your columnsFromBacked and initialize it as empty object.After you data from api is stored in the hook, then you can assign the appropriate values to columnsFromBacked
Solution 1
let columnsFromBackend= {}
const [characters, setCharacters] = useState([]);
useEffect(() => {
const getData = async () => {
await fetch("http://127.0.0.1:6789/api/load_img_data")
.then((res) => res.json())
.then(
(result) => {
const arrayOfObj = Object.entries(result.imgData).map((e) => e[1]);
setCharacters(arrayOfObj);
columnsFromBackend = {
["1"]: {
name: "Terminator Group",
items: characters
}
}
},
(error) => {}
);
};
getData();
}, []);
}
Solution 2
You can implement useEffect hook with dependency on character hook.
sample code
useEffect(()=>{
columnsFromBackend = {...} //your code
}, [character]);

React data content is disappearing on callback

there is something strange happening with my code. My variable data (useState) is randomly empty when I call my callback when onpopstate event is fired.
I have 2 components and 1 hook used like that:
const Parent = props => {
const {downloadData} = useData();
const [data, setData] = useState([]);
const [filteredData, setFilteredData] = useState();
const loadData = async () => setData(await downloadData());
useEffect(() => {
loadData();
}, []);
return <FilterPage data={data} onDataChange={data => setFilteredData(data)} />
}
const FilterPage = ({data, onDataChange} => {
const {saveHistoryData} = useHistoryState('filter', null, () => {
updateFilters();
});
const filter = (filterData, saveHistory = true) => {
let r = data; // data is randomly empty here
...
if(saveHistory)saveHistoryData(filterData);
onDataChange(r);
}
});
// my hook
const useHistoryState = (name, _data, callback) => {
const getHistoryData = () => {
const params = new URLSearchParams(window.location.search);
try{
return JSON.parse(params.get(name));
}catch(err){
return null;
}
}
const saveHistoryData = (data) => {
const params = new URLSearchParams(window.location.search);
params.set(name, JSON.stringify(data || _data));
window.history.pushState(null, '', window.location.pathname + '?' + params.toString());
}
const removeHistoryData = () => {
const params = new URLSearchParams(window.location.search);
params.delete(name);
window.history.pushState(null, '', window.location.pathname + '?' + params.toString());
}
const watchCallback = () => {
callback(getHistoryData());
};
useEffect(() => {
let d = getHistoryData();
if(d)watchCallback();
window.addEventListener('popstate', watchCallback);
return () => window.removeEventListener('popstate', watchCallback);
}, []);
return {getHistoryData, saveHistoryData, removeHistoryData};
}
Any suggestions please
Edit
I'm sorry is not the entire code, just a draft. I download the data using async function. The data is loading fine but is empty only if we call the callback from the hook.
You need to use setData to populate data
First of all you are not calling setData() anywhere.
You are using data but not setData and you are using setFilteredData but not filteredData.
Furthermore it doesn't look like updateFilters() exist within FilterPage.
You are passing onDataChange to <Filterpage> but you are not using the property, only ({data}) which explains why it's empty. You might want to update the FilterPage signature: const FilterPage = ({data, onDataChange}) => {} and use the onDataChange

react state not updating inside callabck

I'm not understanding why the following code, the callback onSocketMessage is not using the new acquisition state. inside the useEffect the state is correctly updated, but the function is not evaluated again...i've also tryed using useCallback with acquisition as dependency but nothing changed.
const Ac = () => {
const [acquisition, setAcquisition] = useState({ data: {} })
const [loading, setLoading] = useState(true)
const socket = useRef(null);
const onSocketMessage = (message) => {
console.log(acquisition) // this is always initial state
let { data } = acquisition
data.input[message.index] = message.input
setAcquisition(prevState => ({ ...prevState, data }));
}
useEffect(() => {
fetchCurrentAcquisition(acquisition => {
setAcquisition(acquisition)
setLoading(false)
socket.current = newSocket('/acquisition', () => console.log('connected'), onSocketMessage);
})
return () => socket.current.disconnect()
}, [])
console.log(acquisition)
You are logging a stale closure you should try the following instead:
const onSocketMessage = useCallback((message) => {
setAcquisition((acquisition) => {
//use acquisition in the callback
console.log(acquisition);
//you were mutating state here before
return {
...acquisition,
data: {
...acquisition.data,
input: {
//not sure if this is an array or not
//assimung it is an object
...acquisition.data.input,
[message.index]: message.input,
},
},
};
});
}, []); //only created on mount
useEffect(() => {
fetchCurrentAcquisition((acquisition) => {
setAcquisition(acquisition);
setLoading(false);
socket.current = newSocket(
'/acquisition',
() => console.log('connected'),
onSocketMessage
);
});
return () => socket.current.disconnect();
//onSocketMessage is a dependency of the effect
}, [onSocketMessage]);

React Hooks FileReader. Warning: unstable_flushDiscreteUpdates: Cannot flush updates when React is already rendering

I try to use FileReader on FileWidgetDropzone after the file is selected.
I use a function with onLoadCallback:
const readFile = (file: any, onLoadCallback: any) => {
var reader = new FileReader();
reader.onload = onLoadCallback;
reader.readAsArrayBuffer(file);
};
I use State to store result data (fileMetadata) from onLoadCallback function:
const [loadedFileReader, setLoadFileReader] = useState(false);
const [fileMetadata, setFileMetadata] = useState<IFileMetadata>(
new FileMetadataValues()
);
const [files, setFiles] = useState<any[]>([]);
The function that uses readFile and update state inside a onLoadCallback:
const getFileExif = (file: any) => {
setLoadFileReader(true);
readFile(file, function(e: any) {
const data = ... e.target.result;
if (data) {
var model = new FileMetadataValues();
...
setFileMetadata(model);
setLoadFileReader(false);
}
}
});
};
Use Effect to get additional file data
useEffect(() => {
if (files.length >0 && !loadedFileReader) {
getFileExif(files[0]);
return () => {
files.forEach(file => URL.revokeObjectURL(file.preview));
};
}
}, [files]);
return (
<div>
<FileWidgetDropzone
...
This component was rerendered each time then the state change is called from getFileExif function (total 4 times).
When I put debugger; before return (... I get an error coon console log: Warning - index.js:1 Warning: unstable_flushDiscreteUpdates: Cannot flush updates when React is already rendering.
Please help to develop a more efficient solution for this example.
A solution was found when use React Dropzone. I use Promises.
Parent component:
const [files, setFiles] = useState<any[]>([]);
const [filesMetadata, setFilesMetadata] = useState<any[]>([]);
Child component:
const getFilesMetadata = (acceptedFiles: object[]) => {
const files = [...acceptedFiles];
parseMeta(files);
};
files.forEach((file: any) => {
let promise = new Promise(resolve => {
const reader = new FileReader();
reader.onabort = () => console.log("file reading was aborted");
reader.onerror = () => console.log("file reading has failed");
reader.onload = () => {
...
resolve(model);
};
reader.readAsArrayBuffer(file);
});
promises.push(promise);
});
Promise.all(promises).then((data) => {
setFilesMetadata(
data.map((fileMeta: FileMetadataValues) =>
Object.assign(fileMeta, fileMeta)
)
);
});
const onDrop = useCallback(
acceptedFiles => {
setFiles(
acceptedFiles.map((file: object) =>
...
)
);
getFilesMetadata(acceptedFiles);
},
[setFiles, getFilesMetadata]
);
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });

How can I initialize in useState with the data from custom hooks?

I'm learning to React Hooks.
And I'm struggling initialize data that I fetched from a server using a custom hook.
I think I'm using hooks wrong.
My code is below.
const useFetchLocation = () => {
const [currentLocation, setCurrentLocation] = useState([]);
const getCurrentLocation = (ignore) => {
...
};
useEffect(() => {
let ignore = false;
getCurrentLocation(ignore);
return () => { ignore = true; }
}, []);
return {currentLocation};
};
const useFetch = (coords) => {
console.log(coords);
const [stores, setStores] = useState([]);
const fetchData = (coords, ignore) => {
axios.get(`${URL}`)
.then(res => {
if (!ignore) {
setStores(res.data.results);
}
})
.catch(e => {
console.log(e);
});
};
useEffect(() => {
let ignore = false;
fetchData(ignore);
return () => {
ignore = true;
};
}, [coords]);
return {stores};
}
const App = () => {
const {currentLocation} = useFetchLocation();
const {stores} = useFetch(currentLocation); // it doesn't know what currentLocation is.
...
Obviously, it doesn't work synchronously.
However, I believe there's the correct way to do so.
In this case, what should I do?
I would appreciate if you give me any ideas.
Thank you.
Not sure what all the ignore variables are about, but you can just check in your effect if coords is set. Only when coords is set you should make the axios request.
const useFetchLocation = () => {
// Start out with null instead of an empty array, this makes is easier to check later on
const [currentLocation, setCurrentLocation] = useState(null);
const getCurrentLocation = () => {
// Somehow figure out the current location and store it in the state
setTimeout(() => {
setCurrentLocation({ lat: 1, lng: 2 });
}, 500);
};
useEffect(() => {
getCurrentLocation();
}, []);
return { currentLocation };
};
const useFetch = coords => {
const [stores, setStores] = useState([]);
const fetchData = coords => {
console.log("make some HTTP request using coords:", coords);
setTimeout(() => {
console.log("pretending to receive data");
setStores([{ id: 1, name: "Store 1" }]);
}, 500);
};
useEffect(() => {
/*
* When the location is set from useFetchLocation the useFetch code is
* also triggered again. The first time coords is null so the fetchData code
* will not be executed. Then, when the coords is set to an actual object
* containing coordinates, the fetchData code will execute.
*/
if (coords) {
fetchData(coords);
}
}, [coords]);
return { stores };
};
function App() {
const { currentLocation } = useFetchLocation();
const { stores } = useFetch(currentLocation);
return (
<div className="App">
<ul>
{stores.map(store => (
<li key={store.id}>{store.name}</li>
))}
</ul>
</div>
);
}
Working sandbox (without the comments) https://codesandbox.io/embed/eager-elion-0ki0v

Resources