So I'm building an app that allows you to chose more than one photo, on chose I set the files in React State then i listed for change for that state with useEffect so I can iterate and convert to base64 using FileRead to preview what I've uploaded. But I'm having a problem that the data I'm getting is weird, some of the files are read and added to the React State and some just appear just as File, here is the screenshot:
Here is the screenshot of the Console Log (can't add the object because is to long)
And here is how I add to the state the files on upload:
<input
className={styles.hidden_input}
type='file'
multiple='multiple'
accept='image/*'
onChange={(event) => {
const files = event.target.files;
if (files) setImages(files);
else setImages(null);
}}
And here is how I convert them when uploads are made:
useEffect(() => {
if (images.length !== 0) {
for (let i = 0; i < images.length; i++) {
let file = images[i];
const reader = new FileReader();
reader.onload = () => {
const single = reader.result;
setImagesStream([...images, single]);
};
reader.readAsDataURL(file);
}
} else {
console.log('No images where found.');
}
}, [images]);
When I try to iterate, just the last image shows the other show blank, because aren't converted.
You need to pass a function to the setState of 'setImagesStream' so it can read from the current value in the updater instead of reading the current value rendered. Here is the docs reference
Something like this should work:
setImagesStream((state) => {
return [...state, single]
});
Related
here I have a question regarding references to creating a feature in the react leaflet.
So, the feature has the following functions =
The user page when they want to upload a location is in the form of a csv file containing latitude and longitude.
When the user clicks the red button above, a popup will appear to upload the csv file.
When finished uploading the csv file, it will go directly to the location based on latitude and longitude.
So my question is, does anyone have a tutorial on how to create a csv upload button that points directly to a map with reactjs and leaflets? Thank you very much
Although you have not asked to use react-leaflet I would advise you to do so because you will end up in a mess when you will have to export the map reference to reuse it across places.
First create a button that will handle the upload of a csv file. There is a really useful guide to do so without the use of libraries like paparse although it simplifies a lot this procedure. Next you need to transform the csv columns to some form of data to use. This is also included in the guide. You end up with an array of csv columns.
Then all you need to do is to create a custom react-leaflet component to render the markers and zoom to the markers viewport.
Also you can clean the csv file once you insert a new one.
function App() {
const [csvToArray, setCsvToArray] = useState([]);
const fileReader = new FileReader();
const csvFileToArray = (string) => {
const csvHeader = string.slice(0, string.indexOf("\n")).split(",");
const csvRows = string.slice(string.indexOf("\n") + 1).split("\n");
const array = csvRows.map((i) => {
const values = i.split(",");
const obj = csvHeader.reduce((object, header, index) => {
object[header] = values[index];
return object;
}, {});
return obj;
});
setCsvToArray(array);
};
const handleOnChange = (e) => {
if (csvFileToArray.length > 0) setCsvToArray([]);
const file = e.target.files[0];
if (file) {
fileReader.onload = function (event) {
const text = event.target.result;
csvFileToArray(text);
};
fileReader.readAsText(file);
}
};
console.log(csvToArray);
return (
<>
<input
type="file"
id="csvFileInput"
accept=".csv"
onChange={handleOnChange}
/>
<Map csvToArray={csvToArray} />
</>
);
}
function RenderCsvToArray({ csvToArray }) {
const map = useMap();
useEffect(() => {
if (csvToArray.length === 0) return;
const myPoints = csvToArray.map(({ Latitude, Longitude }) => [
Latitude,
Longitude
]);
const myBounds = new L.LatLngBounds(myPoints);
map.fitBounds(myBounds);
}, [csvToArray]);
return csvToArray?.map(({ Latitude, Longitude, Title }, index) => (
<Marker key={index} icon={icon} position={[Latitude, Longitude]}>
<Popup>{Title}</Popup>
</Marker>
));
}
You can see the full implementation on the demo
I have also inlcuded two csv files to play with in the form of
Title,Latitude,Longitude
Trinity College,41.745167,-72.69263
Wesleyan University,41.55709,-72.65691
and
Group,Title,Image,Description,Address,Latitude,Longitude
a,Trinity College,https://www.edx.org/sites/default/files/trinity1.jpg,"Not in the link view website more not in the link","300 Summit St - Hartford CT 06106,41.745167,-72.69263
a,Wesleyan University,https://upload.wikimedia.org/wikipedia/commons/4/41/You_are_here_-_T-shirt.jpg,"view website",45 Wyllys Ave Middletown CT 06459,41.55709,-72.65691
My purpose is to upload and listen to an audio file using the WEB Audio API. I have been able to listen to the audio file when selecting it, but having trouble pausing and playing it afterwards. I need to export the file to WAV format aswell. I have created a simple example, any help will be much appreciated
Loading Audio and playing it from file input
const onFileChange = (e) => {
let file = e.target.files[0];
console.log(file);
setFile(file);
let fileReader = new FileReader();
fileReader.onload = function (ev) {
audioContext.decodeAudioData(ev.target.result).then(function (buffer) {
playSound(buffer);
});
};
fileReader.readAsArrayBuffer(file);
};
Play sound using buffer source
const playSound = (buffer, time) => {
source = audioContext.createBufferSource();
source.buffer = buffer;
source.connect(audioContext.destination);
source.start(time);
setIsPlaying(true);
};
I'm facing problem here with pausing and playing:
const onPlayPause = (e) => {
console.log("audioState", audioContext.state);
console.log("duration", audioContext.currentTime);
if (!isPlaying) {
//source.start();
setIsPlaying(true);
} else if (audioContext.state === "running") {
setPlayDuration(audioContext.currentTime);
audioContext.suspend();
setIsPlaying(false);
} else if (audioContext.state === "suspended") {
audioContext.resume();
}
};
Export Audio:
const exportAudioFile = () => {
offlineContext.render().then((buffer) => {
setRenderState('encoding');
const handleMessage = ({ data }) => {
var blob = new window.Blob([new DataView(data)], {
type: 'audio/wav',
});
//blob = new Blob([buffer], { type: "audio/wav" });
const url = window.URL.createObjectURL(blob);
}
window.URL.revokeObjectURL(url);
})};
Codesandboxlink:
https://codesandbox.io/s/react-audiocontext-pause-play-fw20u?file=/src/App.js
I've had my fair share of headaches with persisting things in react functional components. Fortunately, useRef is an awesome tool for just that:
https://reactjs.org/docs/hooks-reference.html#useref
As the documentation says, it essentially returns a container which .current property persists across re-renders.
I forked your code to useRef in action:
https://codesandbox.io/s/react-audiocontext-pause-play-forked-si59u?file=/src/App.js:120-159
Basically, when you load the file, store your shiny new AudioContext in the ref's .current field, and reference that throughout the rest of your component. You can clean it up a bit, IE store the .current in a constant scoped to the function you're using it in.
Two key spots:
export default function App() {
const audioCtxContainer = useRef(null);
...
and
audioCtxContainer.current = new AudioContext();
audioCtxContainer.current
.decodeAudioData(ev.target.result)
.then(function (buffer) {
playSound(buffer);
});
useRef is useful for any mutable object that you want to persist for the lifetime of the component.
Let me know if that helps!
I am using antd's Upload component, its task is to just upload the image and then I grab that image and send it to the API to store it. But I keep getting upload failed message tooltip as I am not using any action prop that they provide. Even their own website has this problem as I'm trying to upload something and it shows failed message but it has been actually uploaded. antd's Upload
I am using useState to save the file const [uploadedImage, setUploadedImage] = useState();
My fileProps looks like this:
const fileProps = {
name: 'file',
multiple: false,
onChange(info) {
if (info.file.status !== 'uploading') {
let reader = new FileReader();
reader.onload = (e) => {
setData({
...data,
image: new File([e.target.result], info.file.name),
});
setIsFileUploaded(true);
}
reader.readAsDataURL(info.file.originFileObj);
setUploadedImage(info.file.originFileObj);
}
},
};
I then pass it to the Upload Component:
<Upload {...fileProps}>
<Button icon={<UploadOutlined />}>Upload Image</Button>
</Upload>
Why does it keep showing Upload error Tooltip even though it is successfully uploading and I can store it? how can I remove this tooltip? I know there is a way to hide the list entirely by using: showUploadList: false but I want to show the uploaded file as sometimes during big uploads I don't have any sort of confirmation if the file is uploading or uploaded.
I have also created codesandbox for it: https://codesandbox.io/s/bold-bash-g3qkj
If you just want to save the file to the state, and not send it automatically to the server, you must set the property beforeUpload.
const fileProps = {
name: "file",
multiple: false,
beforeUpload: () => {
return false;
},
onChange(info) {
if (info.file.status !== "uploading") {
let reader = new FileReader();
reader.readAsDataURL(info.file);
setUploadedImage(info.file);
}
}
};
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 would like to upload multiple images to send them off.
I tried this two ways in handleChange but the formdata is always empty.
I also want to know how to display the image in the react
state = { files: []}
fileChangeHandler = (e) => {
this.setState({ files: e.target.files })
this.setState({ files: [...this.state.files, ...e.target.files] })}
handleSubmit(event) {let fileData = new FormData();
fileData.append('files', this.state.files);
uploadClaimFile(response.data.id, fileData);}
the input
<input type="file" multiple onChange={this.fileChangeHandler} />
The safest way to append to a state array without directly modifying it is to make a shallow copy of the array, add the new items, and then replace the array using setState:
fileChangeHandler = (e) => {
const files = [...this.state.files]; // Spread syntax creates a shallow copy
files.push(...e.target.files); // Spread again to push each selected file individually
this.setState({ files });
}
As for uploading the files, when appending to a FormData object you must append the files one at a time:
handleSubmit(event) {
const fileData = new FormData();
this.state.files.forEach((file) => fileData.append('files[]', file));
// ... Submit fileData
}
Note: The use of [] in naming the data is in accordance with PHP naming conventions when uploading multiple files.
Edit
To answer your last question about displaying multiple uploaded files, you would want to write a method to take those files, create URLs that tie them back to the document, and display them. However, created URLs must be revoked to prevent memory leaks. Thus, it might be a good idea to store them in state to keep track of them, so you can implement it like this:
this.state = { files: [], urls: [] };
setFileUrls(files) {
const urls = files.map((file) => URL.createObjectURL(file));
if(this.state.urls.length > 0) {
this.state.urls.forEach((url) => URL.revokeObjectURL(url));
}
this.setState({ urls });
}
displayUploadedFiles(urls) {
return urls.map((url, i) => <img key={i} src={url}/>);
}
Call setFileUrls in your onChange handler, and call displayUploadedFiles in the render method:
render() {
return (
// ... other stuff
{this.state.urls.length > 0 &&
<Fragment>{this.displayUploadedFiles(this.state.urls)}</Fragment>
}
// ... more stuff
);
}
Multiple adjacent elements should be wrapped in a parent element, which can be a div or a React Fragment.
You can explore this npm module React Drop Zone.