Async Clipboard API "ClipboardItem is not defined" - Reactjs copy image to Clipboard - reactjs

I'm working on React js, I created my app with create-react-app using npm. I was trying to build a button that takes an image and writes it to the clipboard. Fourtunately I found this npm library that seems to work fine! But keeps me thinking why I couldn't use the ¿built-in? Asynchronous Clipboard API to copy the image (the text copy works fine). I read a really enlightening guide here, and kept reading other great guide here, so I tried all the codes suggested, there and in other pages (despite they don't seem to really change the functionality, I got to try). I came with the same error in every try that impedes to compile: "'ClipboardItem' is not defined no-undef". One code for example was this one:
const response = await fetch('valid img url of a png image');
const blob = await response.blob();
await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob})]);
It seems to be simple, easy to follow. The problem is when you need to put the data in a form the Clipboard can read it, make it a blob, because I need the ClipboardItem constructor, and my app seems to be unable to recognize it as such. Keeps returning ClipboardItem is not defined or, if I somehow define it, says it's not a constructor, of course. I tried with other constructors like Blob(), but had the same problem. The last thing kept me thinking that, since I'm new in the programming world, if there is something kinda basic I don't know of the interaction of Web Apis like this one with node or Reactjs, and if there is a solution, of course! Thanks in advance, you guys are great!
Edit: adding the whole component code as requested:
import React from "react";
function TestingClipAPI () {
async function handleScreenshot () {
const response = await fetch('https://i.postimg.cc/d0hR8HfP/telefono.png');
const blob = await response.blob();
await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob})]);
};
return (
<div>
<button onClick={handleScreenshot} id="buttonID">test</button>
</div>
)
};
export default TestingClipAPI;
Possible issue: This might be because of CRA (Create-React-App) config - similar issue. Something like the library linked can be done, create a canvas and copy the image from there.
Solution or a way to make it work anyway: make a call this way before using ClipboardItem:
const { ClipboardItem } = window;
Note: this also works with other constructors like toBlob and HTMLCanvasElement that had the same issue.

Things to look for:
Browser support Clipboard
Secure origin on HTTPS or localhost. See this post.
How the function is being called - in the OP's case - onClick & asynchronous.
The issue is that onClick are not asynchronous by default and you are not awaiting the response and you also have a typo in navigator.clipboard.
const handleScreenshot = async () => {
try {
const response = await fetch(
"https://i.postimg.cc/d0hR8HfP/telefono.png"
);
const blob = await response.blob();
await navigator.clipboard.write([
new ClipboardItem({ "image/png": blob }),
]);
} catch (err) {
console.error(err);
}
}
return (
<button onClick={async () => await handleScreenshot()} id="buttonID">
test
</button>
);
There are tradeoff between inline function and below are alternatives. I'd personally use the latter method.
function handleScreenshot() {
async function screenShot() {
try {
const response = await fetch(
"https://i.postimg.cc/d0hR8HfP/telefono.png"
);
const blob = await response.blob();
await navigator.clipboard.write([
new ClipboardItem({ "image/png": blob }),
]);
} catch (err) {
console.error(err);
}
}
screenShot();
}
return (
<button onClick={handleScreenshot} id="buttonID">
test
</button>
);
Lastly, you can return a chained promise.

Simply add window in front of ClipboardItem like the following
window.ClipboardItem(...)

Unfortunately, as of the time of this answer, ClipboardItem isn't supported in Firefox. (Support can be enabled via an about:config setting; but of course, most Internet users will not have done this.)
Source: https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem#browser_compatibility

Related

Select data from firebase via id gives me a CORS error

I'm deleting data from my firebase db with fetch but I can't figure out how to point to an exact ID.
const deleteHandler = async (id) => {
console.log(id);
await fetch(
`https://react-task-tracker-8e519-default-rtdb.firebaseio.com/tasks.json/${id}`,
{
method: "DELETE",
}
);
I tried it this way, but it gives me a CORS error.
I'm also displaying data from this db, that works fine.
UPDATE: I also want to say that when i console.log the id it gives me the correct one.
(Tl;dr: Try adding '.json' to the end of the endpoint.)
I would recommend reading this page to get a general understanding of what a CORS error is and why it might be happening.
In your case, I would recommend using the Firebase SDK that is best suited to your application. You could start here and follow the setup instructions for whichever is most applicable to your use case (perhaps the node client sdk)?
If you must avoid using the sdks for some reason then I would refer to some other Stackoverflow questions such as this one, which suggests that all Firebase REST endpoints need to end with '.json'.
You just need to add .json at the end of your request and remove .json from tasks.json. like this:-
await fetch(
`https://react-task-tracker-8e519-default-rtdb.firebaseio.com/tasks/${id}.json`,
const deleteHandler = async (id) => {
console.log(id);
await fetch(
`https://react-task-tracker-8e519-default-rtdb.firebaseio.com/tasks/${id}.json`,
{
method: "DELETE",
}
);
Just replace .json text with ${id}.json.
Have a nice day

API caching for next JS

I'm building an app with Next.js... we have 100k+ pages and content changes daily, so using SSR and getServerSideProps.
Some of our data is coming from a headless CMS provider that charges by the request. I'd like to cache the API responses from this server for 24hrs.
What is the best way of going about this?
Is there a common library most folks use to do this?
Just looking for suggestions of approaches I should investigate (or great examples of how to do this).
I used this npm package:
https://www.npmjs.com/package/memory-cache
And then something like this:
import cacheData from "memory-cache";
async function fetchWithCache(url, options) {
const value = cacheData.get(url);
if (value) {
return value;
} else {
const hours = 24;
const res = await fetch(url, options);
const data = await res.json();
cacheData.put(url, data, hours * 1000 * 60 * 60);
return data;
}
}
Then if you want to fetch something with using the cache just call this function. Or it can be used as a midware in the requests. It checks if the data is already in the cache and returns it, or if not - it puts the data into the cache under the key. The key can be anything, I am using the url for instance.
In addition to Tobias Lins' answer:
At least if deploying on Vercel, you can use set Cache-Control headers in getStaticProps, getServerSideProps, API routes, etc to cache responses on Vercel's edge network. This solution does not require any additional dependencies and very minimal code.
api route example - source Vercel
// pages/api/user.js
export default function handler(req, res) {
res.setHeader('Cache-Control', 's-maxage=86400');
res.status(200).json({ name: 'John Doe' });
}
Example in getServerSideProps - Source NextJS
// This value is considered fresh for ten seconds (s-maxage=10).
// If a request is repeated within the next 10 seconds, the previously
// cached value will still be fresh. If the request is repeated before 59 seconds,
// the cached value will be stale but still render (stale-while-revalidate=59).
//
// In the background, a revalidation request will be made to populate the cache
// with a fresh value. If you refresh the page, you will see the new value.
export async function getServerSideProps({ req, res }) {
res.setHeader(
'Cache-Control',
'public, s-maxage=10, stale-while-revalidate=59'
)
return {
props: {},
}
}
I believe you'd want to use:
res.setHeader('Cache-Control', 's-maxage=1440000')
Here are some other useful links for caching on Vercel:
https://vercel.com/docs/concepts/functions/edge-caching
https://vercel.com/docs/concepts/edge-network/overview
https://vercel.com/docs/concepts/edge-network/caching
https://vercel.com/docs/concepts/edge-network/headers
For your specific case, you also may want to look into using getStaticPaths with getStaticProps. You can use fallback: true on getStaticPaths to only build pages when they're visited (you can still build your post popular pages at initial build time).
https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required
I know this is an old post, but for others googling (at least those deploying on Vercel), these solutions should help where revalidate in getStaticProps does not.
You could use getStaticProps from Next.js for SSG
They currently have a revalidate property that you can return, that defines how often the content should be re-fetched.
Take a look here:
https://nextjs.org/blog/next-9-5#stable-incremental-static-regeneration
This is how we did it without any 3rd party libraries, as in our use-case we only had to cache a relatively smaller amount of global data(header/footer menus) which was shared across the site.
The data was coming from a CMS via GraphQL.
We ran an async method getGlobalData on each page from on getStaticProps method and then returned the cached data to the page component via props.
import fs from 'fs';
import path from 'path';
// Cache files are stored inside ./next folder
const CACHE_PATH = path.join(__dirname, 'globalData.json');
export default async function getGlobalData() {
let cachedData;
// #1 - Look for cached data first
try {
cachedData = JSON.parse(fs.readFileSync(CACHE_PATH, 'utf8'));
} catch (error) {
console.log('❌ CACHE NOT INITIALIZED');
}
// #2 - Create Cache file if it doesn't exist
if (!cachedData) {
// Call your APIs to-be-cached here
const data = await fetchGlobalData();
// Store data in cache files
// this always rewrites/overwrites the previous file
try {
await fs.writeFileSync(
CACHE_PATH,
JSON.stringify(data),
err =>throw err
);
console.log('💾 CACHE FILE WRITTEN SUCCESSFULLY');
} catch (error) {
console.log('❌ ERROR WRITING MEMBERS CACHE TO FILE\n', error);
}
cachedData = data;
}
return cachedData;
}
Call getGlobalData method from getStaticProps.
export async function getStaticProps({ preview = false }) {
const globalData = await getGlobalData();
// call other page-specific/non-shared APIs here
// ...
return { props: { globalData } };
}
References
https://flaviocopes.com/nextjs-cache-data-globally/
Note if you get an error saying fs or path is unknown or invalid, then please understand that, the above code is supposed to be running or referenced "serverside" i.e only inside getStaticProps or getServerSideProps. If you import and reference it "browser-side", say somewhere inside your components or on the page (other than methods mentioned above), then you will get an error, as there is no filesystem(fs) or path modules on browser. They are only available on node.

Can't get the Generic Sensor API to work in a React app

I'm trying to implement the Generic Sensor API in a React app.
https://www.w3.org/TR/generic-sensor/#the-sensor-interface
I keep getting an error when I try to implement any of the sensors in my code.
For example:
var sensor1 = new AmbientLightSensor();
I get the error: Cannot find name: 'AmbientLightSensor'.
I assume that I need an import statement in my code. All of the examples I've found only include LitElement. I've even tried that but still get the unknown error.
What import statements do I need in my typescript code?
What npm packages do I need?
Below is the typescript code I'm using.
I'm getting a typescript error:
/Users/scoleman/dev/current/bigbrother/src/utility/testAccel.ts(14,24):
Cannot find name 'AmbientLightSensor'. TS2304
export const testAccel = async (
databaseName: string,
) => {
const {state} = await navigator.permissions.query({
name: "ambient-light-sensor"
});
if (state !== "granted") {
console.warn("You haven't granted permission to use the light sensor");
return;
}
const sensor = new AmbientLightSensor();
sensor.addEventListener("reading", () => {
console.log(sensor.illuminance);
});
sensor.addEventListener("error", (err: any) => {
console.error(err);
});
sensor.start();
};
I was able to get these api's running using the polyfill at:
https://github.com/kenchris/sensor-polyfills
This would depend entirely on the browser you are using. I don't think FireFox supports it at the moment so I will focus on Chrome.
Firstly, you might need to be serving your site over HTTPS. It seems like this almost varies from permission to permission and also some are available on a localhost URL no matter what.
Secondly, for Chrome, you have to enable the "Generic Sensor Extra Classes" flag in Chrome at the chrome://flags/#enable-generic-sensor-extra-classes page.
Next, you need to make sure that have permission from the user to use the sensor, then you could actually use it. A snippet that would check that is as follows:
(async function(){
const {state} = await navigator.permissions.query({
name: "ambient-light-sensor"
});
if (state !== "granted") {
console.warn("You haven't granted permission to use the light sensor");
return;
}
const sensor = new AmbientLightSensor();
sensor.addEventListener("reading", () => {
console.log(sensor.illuminance);
});
sensor.addEventListener("error", err => {
console.error(err);
});
sensor.start();
}());

Uploading an image to Azure Blob Storage using React

I want to upload an image to Azure Blob Storage using React.
I've tried a lot of examples and none of them work.
The one that seemed the best was this one but still didn't manage to get it working on React.
What I'm trying right now is to use the createContainerIfNotExists method just to test and the error is Cannot read property createBlobServiceWithSas of undefined
My code is the following:
import AzureStorage from 'azure-storage';
const account = {
name: 'x',
sas: 'x',
};
const blobUri = `https://${account.name}.blob.core.windows.net`;
const blobService = AzureStorage.Blob.createBlobServiceWithSas(blobUri, account.sas);
export const createContainer = () => {
blobService.createContainerIfNotExists('test', (error, container) => {
if (error) {
// Handle create container error
} else {
console.log(container.name);
}
});
};
export default createContainer;
According to my research, because you develop A React application, we can not use the createBlockBlobFromBrowserFile method. We just can use the method in the browser. For more details, please refer to the document.
According to the situation, I suggest you use the other method(such as uploadStreamToBlockBlob) to upload image with V10 sdk. For more details, please refer to https://learn.microsoft.com/en-us/javascript/api/#azure/storage-blob/?view=azure-node-latest

admin-on-rest Using PATCH method

I am a junior node developer and am trying out admin on rest to quickly run up an admin panel for my json api. However, all of my update requests use patch instead of put. I attempted revising the UPDATE method in my restClient but this seems wrong (the rest of the methods are removed for brevity)
export default (apiUrl, httpClient = fetchJson) => {
const convertRESTRequestToHTTP = (type, resource, params) => {
let url = ''
const options = {}
switch (type) {
case UPDATE:
url = `${apiUrl}/${resource}/${params.id}`
options.method = 'PATCH'
options.body = JSON.stringify(params.data)
break
return { url, options }
}
}
To me this makes sense but when I try to edit an object I get back HTTP/1.1 404 Not Found <pre>Cannot PUT </pre>
I know that that this wasn't possible with previous versions but I read this https://marmelab.com/blog/2017/03/10/admin-on-rest-0-9.html#http-patch but was a little confused on how it works? I guess I just don't know where to start with this.
if problem still is actual now, please check some places which are using by me to set my customRestClient.
// App.js
import customRestClient from './customRestClient';
in my case i'm using httpClient to add custom headers:
import httpClient from './httpClient';
below:
const restClient = customRestClient('my_api_url', httpClient);
and finally:
<Admin title="Admin Panel" restClient={restClient}>

Resources