How to load image from blob and show Image preview - reactjs

I'm letting users upload a cover image.
I'm using ant design image upload. So it provides UI and the ability to crop images by default.
onst [fileList, setFileList] = useState([]);
const onPreview = async (file) => {
let src = file.url;
if (!src) {
src = await new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(file.originFileObj);
reader.onload = () => resolve(reader.result);
});
}
const image = new Image();
image.src = src;
const imgWindow = window.open(src);
imgWindow.document.write(image.outerHTML);
};
return (
<div>
<UploadButton
fileList={fileList}
beforeUpload={() => false}
onChange={onChange}
onPreview={onPreview} aspect={2} listType="picture" />
<div/>
)
Once the cropping is done, before reaching the server, I want to give users the ability to preview the imageso they can confirm it before upload.
On successfully, uploading the image, I accessed the thumb URL to show users. It's blurry and distorted.
So I went on check the network section and I found the blob as shown in Image, which gives me a quality image that I can show in the product.
I'm new to programming. I was wondering, how to access it.

You can create a temporary image url out of a blob object.
const imageUrl = URL.createObjectURL(imageBlob) // imageBlob is of type blob

Related

Ant Design Upload component rescales images to 200x200 [React]

I met an issue at Ant Design <Upload /> component. When I upload an image it sends two Img typed requests at the Network console. First contains my orginal image (tested at 650x650 image size), second contains transformed image to 200x200. And here is my question: Why image is rescaled to lower resolution and how can I prevent that? I want to upload orignal images with transparency if that is required.
In my case Upload component is used to upload single image. Component is placed on modal component that is why I prepared dummyRequest.
const UploadImage = ({
defaultValue,
maxFilesAmount,
}: IUploadImageProps<any>): JSX.Element => {
const [fileList, setFileList] = useState<IUploadFile<any>[]>(
defaultValue ?? []
)
const onChange = useCallback(
(info: IUploadChangeParam<IUploadFile<unknown>>): void => {
setFileList(info.fileList)
},
[setFileList]
)
const onPreview = useCallback(async (file: IUploadFile): Promise<void> => {
let src: string | undefined = file?.url ?? file?.thumbUrl ?? ''
if (!_.isNull(src)) {
src = await convertImportImageToBase64(src)
}
const image: HTMLImageElement = new Image()
const imgWindow: Window | null = window.open(src)
image.src = src
imgWindow?.document.write(image.outerHTML)
}, [])
const dummyRequest = useCallback(({ onSuccess }: IUploadRequestOption) => {
setTimeout(() => {
//#ts-ignore
onSuccess('ok')
}, 0)
}, [])
const beforeUpload = useCallback((): false => {
//Required to ignore Ant Desing default converting uploaded image
//For example that removes transparency from png files
return false
}, [])
return (
<Upload
accept={'.jpg, .jpeg, .png'}
listType={'picture-card'}
beforeUpload={beforeUpload}
fileList={fileList}
onChange={onChange}
onPreview={onPreview}
customRequest={dummyRequest}
>
{_.size(fileList) < (maxFilesAmount ?? 1) && 'Upload'}
</Upload>
)
}
At the start I had also a problem with losing transparency at .png images. Image still was defined as png but transparent background got a white color. Used beforeUpload fix that, I not sure why but okej :).
What can I do more to fix this issue? Thanks in advance.

ReactJS-Leaflet: upload csv file in leaflet

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

AntD's Upload keeps showing failed tooltip but it is uploading successfully

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

Initialize / Eager Load React Component in Background on App Start for later usage

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.

Convert File type to Data URI - React-Dropzone

I am having trouble integrating React-dropzone with FeathersJS Upload
I have successfully implemented the RESTful upload when you POST a datauri to my Upload endpoint. { uri: data:image/gif;base64,........}
My issue is when selecting a file in react-dropzone and submitting the form, I am seeing a File type... It seem's I need to somehow convert that to a data URI.
This should be handled by Dauria... But I think my issue is in my POST request, not having the file property set with the correct file format. Should I be converting the File to FormData?
Here is one way to do it from File object:
Using Image and FileReader allows you to get width, height and base64 data:
onDrop = (acceptedFiles, rejectedFiles) => {
const file = acceptedFiles.find(f => f)
const i = new Image()
i.onload = () => {
let reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => {
console.log({
src: file.preview,
width: i.width,
height: i.height,
data: reader.result
})
}
}
i.src = file.preview
}

Resources