I'm building an upload files functionality using dropzone and react, my issue is that this application should support thousands of images and once I got about 1500 images it collapse and stop sending requests, then in the browser I got this issue:
Failed to load resource: net::ERR_INSUFFICIENT_RESOURCES
I saw some workarounds to create batches, but honestly don't know how to create it, since I'm processing the uploads one by one using async functions, this is my code:
const Dropzone = ({stateChanger, folder, props, ...rest}) => {
let container;
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
let { id } = useParams();
const [files, setFiles] = useState([]);
const {getRootProps, getInputProps} = useDropzone({
onDrop: async acceptedFiles => {
stateChanger(true)
setFiles(acceptedFiles.map(file => Object.assign(file, {
preview: URL.createObjectURL(file),
uid: uuidv4()
})));
const url = `${API_URL}/projects/upload`;
let requestArr = [];
await asyncForEach(acceptedFiles, async (file) => {
console.log('file',file)
var formData = new FormData();
formData.append('file', file);
formData.append('projectId', id);
formData.append('rootfolder', folder);
console.log('formData',formData)
requestArr.push(
axios
.post(url,formData)
.then((rst) => {
console.log('rst',rst)
var elem = document.getElementById(file.uid);
var html = elem.innerHTML;
elem.innerHTML = `<span class="upload-success" >Uploaded</span>`;
})
.catch((error) => {
console.log('error', error);
var elem = document.getElementById(file.uid);
var html = elem.innerHTML;
elem.innerHTML = `<span class="upload-error" >Error uploading</span>`;
})
);
});
Promise.all(requestArr).then(() => {
console.log('resolved promise.all')
stateChanger(false)
});
}
});
const thumbs = files.map(file => (
<div className="thumb" key={file.name}>
<div className="thumbList">
{file.path} - {file.size} bytes - <span id={file.uid}><span className="upload-pending" >Uploading</span></span>
</div>
</div>
));
useEffect(() => {
// Make sure to revoke the data uris to avoid memory leaks
files.forEach(file => URL.revokeObjectURL(file.preview));
}, [files]);
return (
<section className="container">
<ToastContainer
ref={ref => container = ref}
className="toast-top-right"
/>
<div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
<img src={uploadImage} />
<br />
<p>Drag and drop your files, or click the button to upload</p>
<button className="btn primary-btn-active width50">Upload from computer</button>
</div>
<aside >
{thumbs}
</aside>
</section>
);
}
export default Dropzone
And the implementation pretty standard:
<Dropzone stateChanger={setNextDisable} folder={folder} />
Any suggestion?
Why would you need 1500, 5000 images at once?
The user can focus on only so much on the screen at once - and then you have scroll bars which work but is not ideal.
The solution in many places is to have it paginated (10, 20, 50) items on screen at once.
More than that is asking for trouble - as a developer you might have a more powerful PC than an average user - so likely most of your users will have more bottleneck problems, performance issues.
The solution in my opinion would be to reduce the number of items on screen because it doesn't make sense to have that massive amount of assets, and more than a 20 items and user can't focus or bother to have a look or try to do stuff.
Resource consumption has always been a problem that needs to be researched. The processing of large amounts of data is also a similar problem. if only upload. package and compress before uploading.
The problem is not related to Dropzone library. It just means your server, whatever you use, cannot handle such amount of traffic/connections. If you really need you should probably dedicate more resources as the error message states.
Related
I'm currently building some functionality in my React web app to upload a picture, display it on the client (this works), but also store the photo details so they can be sent in an axios post to my backend where the image gets stored in the file system.
Here's my code to now:
const SetupDetails = () => {
const [previewPhoto, setPreviewPhoto] = useState(null)
const [photoFile, setPhotoFile] = useState(null)
const uploadPhoto = (e) => {
e.preventDefault()
setPreviewPhoto(URL.createObjectURL(e.target.files[0]))
setPhotoFile(e.target.files[0])
}
const buttonClick = (e) => {
e.preventDefault()
console.log(previewPhoto)
console.log(photoFile)
axios.post("/api/uploadProfilePicture", {
photoFile: photoFile
})
}
const deletePhoto = (e) => {
e.preventDefault()
setPreviewPhoto(null)
setPhotoFile(null)
}
return (
<label for="photo-upload" className="setup-photo-label">Upload</label>
<input className="setup-photo-input" id="photo-upload" type="file" onChange={uploadPhoto} />
{
previewPhoto ?
<div className="preview-photo-container">
<img className="preview-photo" src={previewPhoto} alt="A small thumbnail uploaded by the user." />
<button className="preview-photo-delete" onClick={deletePhoto}>Delete</button>
</div> : null
}
<button className="setup-continue-button" onClick={buttonClick}>Continue</button>
)
}
The basic premise is:
The input calls a function during 'onChange' which updates state to store the details of the photo
The 'photoFile' details are posted to the backend to be stored in the backend
The 'previewPhoto' is used to display a thumbnail of the image to the user
However, the photoFile is returned as undefined, or an empty object, every time.
I'm not 100% sure where I'm going wrong, as the console.log of the 'photoFile' shows the details of the photo, so I'd have thought I could just post that to the backend.
Any advice here would be really appreciated!
I'm trying to build a project that uses infinite scrolling to display the user information in the ui taken from an external API. I've managed to fetch the data successfully from the API and also apply the infinite scrolling methodology after a lot of googling.
Now, I'm trying to make a dynamical routing work, so basically, when the user clicks on the card containing the user information that I'm getting from the API, It should be able to redirect to its personal page where there's gonna be even more personal data. So, I'm using routing for that and I managed to do that as well.
In the User.jsx file, I got the personal information about one single user that I get from the API. Also, on this page, I also have a list of this single user's friends and when I click on those friend's card, I should be able to redirect to their own personal page. I've managed to do that in a way, the functionality works, but nothing changes in the ui unless if I refresh the page after clicking the friend's card, after that, page displays the user and their personal data correctly. So that's essentially what I'm trying to fix, when I click on the card of user's friend list, the url changes immediately from localhost:3000/users/1 to localhost:3000/users/2, but the ui changes only after I refresh the page.
I'd appreciate any tips and help I can get, thank you in advance, here's the
User.jsx
const params = useParams();
const [users, setUsers] = useState();
const [items, setItems] = useState([]);
const [isFetching, setIsFetching] = useState(false);
const [page, setPage] = useState(1);
// Getting the single user
useEffect(()=>{
async function fetchData(){
const res = await axios(`http://sweeftdigital-intern.eu-central-1.elasticbeanstalk.com/user/${params.id}`);
console.log(res.data);
setUsers(res.data);
}
fetchData()
},[]);
// Getting the list of friends
const fetchData = (setItems, items) => {
let url = `http://sweeftdigital-intern.eu-central-1.elasticbeanstalk.com/user/${params.id}/friends/1/20`;
axios.get(url).then((res) => {
setItems([...items, ...res.data.list]);
console.log(res);
});
};
// Browsing more friends by changing the page from API
const moreData = () => {
let url = `http://sweeftdigital-intern.eu-central-1.elasticbeanstalk.com/user/${params.id}/friends/${page}/20`;
axios.get(url).then(res => {
setItems([...items, ...res.data.list]);
setPage(page+1)
setIsFetching(false)
});
}
// infinite scrolling methodology
const isScrolling =()=>{
if(window.innerHeight + document.documentElement.scrollTop!==document.documentElement.offsetHeight){
return;
}
setIsFetching(true)
}
useEffect(()=>{
fetchData(setItems,items);
window.addEventListener("scroll", isScrolling);
return () => window.removeEventListener("scroll", isScrolling);
},[]);
useEffect(() => {
if (isFetching){
moreData();
}
}, [isFetching]);
if(!users) return <p>Fetching...</p>
return (
<div>
<div className='container'>
<img src={users.imageUrl}/>
<h1 className='info'>Info</h1>
<div className='card-div'>
<div className='name-div'>
<h3 style={{fontWeight:"bold", fontSize:"30px", color:"#FF416C"}}>{users.prefix} {users.name} {users.lastName}</h3>
<h3 style={{fontStyle:"italic", fontSize:"22px"}}>{users.title}</h3>
</div>
<div className='details-div'>
<h3>Email: {users.email}</h3>
<h3>Ip Address: {users.ip}</h3>
<h3>Job Area: {users.jobArea}</h3>
<h3>Job Type: {users.jobType}</h3>
</div>
</div>
<h1 className='adress'>Address</h1>
<div className='address-div'>
<h3 style={{fontSize:"25px"}}>{users.company.name} {users.company.suffix}</h3>
<div>
<h3>City: {users.address.city}</h3>
<h3>Country: {users.address.country}</h3>
<h3>State: {users.address.state}</h3>
<h3>Street Address: {users.address.streetAddress}</h3>
<h3>ZIP: {users.address.zipCode}</h3>
</div>
</div>
</div>
<div className='friends-div'><h1 className='friends-h1'>Friends</h1></div>
<div className="card-container">
{items.map((user,key) => (
<Card key={key} id={user.id} prefix={user.prefix} name={user.name} lastName={user.lastName} image={user.imageUrl} job={user.title}/>
))}
</div>
</div>
)
You need to add your params.id to the useEffect's dependency list in order to know when it should try to update the UI
useEffect(()=>{
async function fetchData(){
const res = await axios(`http://sweeftdigital-intern.eu-central-1.elasticbeanstalk.com/user/${params.id}`);
console.log(res.data);
setUsers(res.data);
}
fetchData()
},[params.id]);
Below I have the index page of a Next.JS app configured with an AWS S3 backend:
import styles from "../styles/Home.module.css";
import { useState, useEffect } from "react";
import { Storage } from "aws-amplify";
export default function Home() {
const [songs, setSongs] = useState(null);
useEffect(() => {
fetchSongs();
}, []);
async function fetchSongs() {
let songKeys = await Storage.list("");
console.log("before signing", songKeys);
songKeys = await Promise.all(
songKeys.map(async (k) => {
const signedUrl = await Storage.get(k.key);
return signedUrl;
})
);
console.log("after signing", songKeys);
setSongs(songKeys);
}
async function onChange(event) {
const file = event.target.files[0];
const result = await Storage.put(file.name, file, {
contentType: "audio/*",
});
console.log(result);
fetchSongs();
}
return (
<div className={styles.container}>
<main className={styles.main}>
<h1 className={styles.title}>Music Streaming Site</h1>
<input type="file" onChange={onChange} accept="audio/*" />
{songs &&
songs.map((song) => (
<div key={song}>
<audio controls>
<source src={song} type="audio/mpeg" />
</audio>
</div>
))}
</main>
</div>
);
}
Currently, when I upload two files of the same name, AWS takes the first file, and the UI updates. But when the second file of the same name is uploaded, AWS takes it, and replaces the original file with it, or in other words, modify the file since the Last Modified column of my AWS console gets a new timestamp at the time the second file was uploaded.
I would like unique keys to be generated each time I upload a file. I would also like to be able to get the file by these unique keys individually, and present them via the UI.
If there's any way for AWS to prevent two of the same file with different names from being uploaded, that would be cool too.
I'm writing a React App, where I have a Fallback component, which gets displayed when something goes wrong, for example: network is down, API isn't reachable, unknown route, etc.
This component will fetch some URLs of cat pictures and displays a slide show.
But of course this isn't possible when the network is down.
Though I'd like to somehow create and initialize this component in the background when the App starts, so everything is ready in the case of emergency.
Additional info: The Fallback component will be used as child component of different views. So it's not possible to simply mount it in App.jsx and use CSS visibility: hidden / visible to hide and display it.
Is this possible and does someone know how to do it?
EDIT: Example code
const Fallback = () => {
// will contain urls like:
// - https://cats.example.org/images/foo.jpg
// - https://cats.example.org/images/bar.png
// - https://cats.example.org/images/42.gif
const [urls, setUrls] = useState([]);
useEffect(() => {
fetch('https://catpictures.example.org')
.then(response => response.json())
.then(data => setUrls(data));
}, []);
// this should be cached somehow:
return (
<div>
{urls.map(url =>
<img src={url} />
}
</div>
);
}
You can do this and I've done it in big production apps by simply creating a new Image() and setting the src. The image will be preloaded when the component is first rendered.
const LoadingComponent() {
useEffect(() => {
const img = new Image();
img.src = imgUrl;
}, []);
return null; // doesn't matter if you display the image or not, the image has been preloaded
}
It could even become a hook such as useImagePreloader(src) but that's up to you.
Here is a Sandbox with a working version.
Steps to try it:
Create an incognito window, open devtools and check the network tab, search for "imgur". The image is loaded.
Set the network offline or disconnect from your WIFI.
Click on the Show Image button. The image will display correctly.
This solution will always work provided your cache settings for images are set correctly (usually they are). If not, you can save the images to blobs and get a URL to that blob, that will work 100% of times.
As you noted that you need an array of images, you can do that same code inside a loop and it will work just fine, images will still be cached:
const images = ['first.jpg', 'second.png', 'etc.gif'];
images.forEach(imageUrl => {
const img = new Image();
img.src = image
});
How about a service worker that would cache your assets and then serve them when offline? Then you could send a message with the URL to change to notify your "app" it is back online with some new content.
Thre is a working example here: https://serviceworke.rs/strategy-cache-update-and-refresh_demo.html
var CACHE = 'cache-update-and-refresh';
self.addEventListener('install', function(evt) {
console.log('The service worker is being installed.');
evt.waitUntil(caches.open(CACHE).then(function (cache) {
cache.addAll([
'./controlled.html',
'./asset'
]);
}));
});
function fromCache(request) {
return caches.open(CACHE).then(function (cache) {
return cache.match(request);
});
}
function update(request) {
return caches.open(CACHE).then(function (cache) {
return fetch(request).then(function (response) {
return cache.put(request, response.clone()).then(function () {
return response;
});
});
});
}
function refresh(response) {
return self.clients.matchAll().then(function (clients) {
clients.forEach(function (client) {
var message = {
type: 'refresh',
url: response.url,
eTag: response.headers.get('ETag')
};
client.postMessage(JSON.stringify(message));
});
});
}
self.addEventListener('fetch', function(evt) {
console.log('The service worker is serving the asset.');
evt.respondWith(fromCache(evt.request));
evt.waitUntil(
update(evt.request)
.then(refresh)
);
});
Local Storage
On page/App load;
Get each image
Save base64 data to localStorage
On network fail
Render <FallBack />
<FallBack />
Read localStorage
Render base64 images
Small example
We use fetch to get the cat images in the <App/> component
Save those to the localStorage
(NOTE: StackSnippet doesn't allow localStorage, so please test it on JSFiddle)
We use a useState to 'fake' the network status
// Init
const { useState } = React;
// <Fallback />
const Fallback = () => {
// Get all localstorage items
let ls = { ...localStorage };
// Get all starting with 'image_'
ls = Object.keys(ls).filter(key => key.startsWith('image_'));
// Render images
return (
<div>
<p>{'FallBack'}</p>
{
(ls.length < 1)
? 'Unable to find cached images!'
: (
ls.map((key) => {
// Get current key from localstorage
const base64 = localStorage.getItem(key);
// Render image
return <img src={base64} />;
})
)
}
</div>
);
}
// <App />
const App = ({title}) => {
const [network, setNetwork] = useState(true);
const [urls, setUrls] = useState([ 'https://placekitten.com/200/300', 'https://placekitten.com/200/300']);
// Render Fallback on lost network
if (!network) {
return <Fallback />;
}
// While network is active, get the images
urls.forEach((url, index) => {
fetch(url)
.then(response => response.blob())
.then(blob => {
// Convert BLOB to base64
var reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function() {
// Write base64 to localstorage
var base64data = reader.result;
localStorage.setItem('image_' + index, base64data);
console.log('Saving image ' + index + ' to localstorage');
};
});
})
return (
<div>
<p>{'App'}</p>
<p>Press me to turn of the internet</p>
<button onClick={() => setNetwork(false)}>{'Click me'}</button>
</div>
);
};
// Render <App />
ReactDOM.render(<App />, document.getElementById("root"));
JSFiddle Demo
Pros;
LocalStorage will not be cleared, if the same app is loaded the next day, we don't need to get those images again
Cons;
There's a size limit for LocalStorage
You can manually add a resource to the cache by using a preloaded <link>:
<link rel="preload" as="image" href="https://cats.example.org/images/foo.jpg">
Put this inside your index.html and use it from cache when needed by using the same href.
I'm trying to get some images from firebase storage loaded into a react-responsive-carousel. The problem is that only 1 or 2 or even none of the images are loaded (there are at least 10 images). This is what I'm trying:
const [imagenes, setImagenes] = useState([]);
useEffect(() => {
const fetchedImagenes = [];
firebase.storage().ref().child('PR/Gallery').listAll().then(function (result) {
result.items.forEach(function (itemRef) {
itemRef.getDownloadURL().then(function (link) {
const fetchedImagen = link
fetchedImagenes.push(fetchedImagen)
})
});
setImagenes(fetchedImagenes)
})
}, [])
And this is the carousel code:
<Carousel dynamicHeight={true}>
{imagenes.map(imagen => {
return <div>
<img src={imagen} />
</div>
})}
</Carousel>
My guess is that the imagenes array is not filled when I show the images, so is there any way that I can wait for the imagenes array to be filled and then show the images?
You're now calling setImagenes(fetchedImagenes) before any of the fetchedImagenes.push(fetchedImagen) has been called, so you're setting an empty array.
The quick fix is to set the array to the state whenever a download URL is determined by moving it into the then callback:
firebase.storage().ref().child('PR/Gallery').listAll().then(function (result) {
result.items.forEach((itemRef) => {
itemRef.getDownloadURL().then((link) => {
const fetchedImagen = link
fetchedImagenes.push(fetchedImagen)
setImagenes(fetchedImagenes)
})
});
})
This will work, but may result in some flicker as you're updating the UI whenever any download URL is determined.
If you want to only update the state/UI once, you're looking for Promise.all and it'll be something like:
firebase.storage().ref().child('PR/Gallery').listAll().then(function (result) {
const promises = result.items.map((itemRef) => itemRef.getDownloadURL());
Promise.all(promises).then((urls) =>
setImagenes(urls)
});
})