how to write to a json file in react - arrays

I have a json file that I will like to add data into it.
The json file contain:
[{"name":"Flossi","image":"https://robohash.org/istevelitut.png?size=50x50&set=set1","price":49,"info":"Cras dui."},
{"name":"Rab","image":"https://robohash.org/voluptatumsuntiste.png?size=50x50&set=set1","price":64,"info":"Phasellus insi."}]
I tried to use fs but I understand it cannot be done with react.
Tried to install this package to npm:
https://www.npmjs.com/package/write-json-file
But get an error on the compiling..
Help?

NodeJS using the built in module fs to read and write and React-Native doesnt have one. I would recommend trying to use react-native-fs, although there are some Android limitations. After following the installation setup, you have to ask yourself where you want to save the JSON. If its in the app internal storage, there's no problem, but if you want to save to external storage there's additional setup that may ultimately just not work (I've experienced this).
import RNFS from 'react-native-fs';
import { Alert, Platform, PermissionsAndroid,} from 'react-native';
const saveFile= (saveLocation,data,encoding='utf8')=> {
let dataString = JSON.stringify(data,null,2)
RNFS.writeFile(saveLocation,dataString,encoding)
.then(()=>Alert.alert('Saved the data!')
.catch((err)=>Alert.alert("Failed to save data!",err.message)
}
// you may or may not need this
const getFilePermissionsAndroid = async ()=>{
const permissions = [
PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
]
// map permissions to whether or not they are granted
let checkPermissions = await Promise.all(permissions.map(permission=>
PermissionsAndroid.check(permission)
))
// filter for ungranted permissions
let permissionsToRequest = permissions.filter((permission,i)=>!checkPermissions[i])
// if all are granted theres nothing to do
if (permissionsToRequest.length == 0)
return true
// request the ungranted permissions
let requestResult = await PermissionsAndroid.requestMultiple(permissionsToRequest);
// I think requestResult is an array of Booleans but im not sure
return Array.isArray(requestResult) ?
// if array return true only if all permissions are granted
requestResult.every(permission=>Boolean(permission)) :
requestResult
}
const data = [
{
name: "Flossi",
image: "https://robohash.org/istevelitut.png?size=50x50&set=set1",
price: 49,
info: "Cras dui."
},
{
name: "Rab",
image: "https://robohash.org/voluptatumsuntiste.png?size=50x50&set=set1",
price: 64,
info: "Phasellus insi."
}
];
// add some stuff
data.push({
name:"blah blah",
image:"https://i.kym-cdn.com/entries/icons/mobile/000/003/406/trap-card-2.jpg",
price:Math.floor(Math.random()*69),
info:"Blah blah info"
});
// save to file
if(Platform.OS == 'android'){
if(!await getFilePermissionsAndroid())
Alert.alert('Writing to external storage may fail without these permissions','Writes to internal storage should be fine tho');
}
// check out documentation for more paths
let path = RNFS.DocumentDirectoryPath + 'data.json';
saveFile(path,data);

Related

Upload react-pdf dynamically generated file to Sanity using NextJS

I'm working on an e-commerce app built on NextJS and Sanity, so far I've made some mock products with all the necessary requirements, a user login system and checkout. I've been trying to make an invoice system so that when the user confirms an order 3 things must happen:
send all the order data to a react-pdf component and generate the invoice(working)
post the invoice file to the sanity schema so that the user has access to it when he goes to his order history page(not working)
email both the company and the client about the order(not implemented yet but I can do it)
ReactPDF allows me to access the pdf through a hook that returns me the blob of the file and the URL. I've tried to POST both of them but the url returned 404 and the blob didn't upload at all.
Searched the docs of both ReactPDF and Sanity and I couldn't find anything, although I think it has to do something with this endpoint from Sanity:
myProjectId.api.sanity.io/v2021-06-07/assets/files/myDataset
This is how I POST the order to my sanity studio
const { data } = await axios.post(
'/api/orders',
{
user: userInfo,
invoice_id: orders.length + 1,
orderItems: cartItems.map((item) => ({
...item,
slug: undefined
})),
billingData,
paymentMethod,
itemsPrice,
taxPrice,
totalPrice
},
{
headers: {
authorization: `Bearer ${userInfo.token}`
}
}
);
I've tried making 2 POST requests, one for the invoice_file alone, trying to post the blob or the url but none did work. The schema for invoice file was updated for the type of post each time so I'm 99% sure that wasn't the issue, anyway here's how the schema for invoice_file looks as for file:
{
name: 'invoice_file',
title: 'Invoice',
type: 'file',
options: {
storeOriginalFilename: true
}
},
If there would be any other code snippets relevant please let me know.
I really don't know how to find the solution for this as it's the first time trying to do such thing, so help would be much appreciated.
Thanks in advance!
I apologies as I'm not really active here but it's hard to pass on your question especially as I'm working on something similar. There's probably other ways to do this but I suggest you work use the official Sanity client. There's a specific section in the README that tells us how to do the file uploads or here.
So here's kinda the very small snippet:
import {
Document,
pdf,
} from "#react-pdf/renderer";
const doc = <Document />;
const asPdf = pdf([]); // {} is important, throws without an argument
asPdf.updateContainer(doc);
const blob = await asPdf.toBlob();
// `blob` here is coming from your react-pdf blob
const fileName = "customfilename.pdf";
client.assets.upload("file", blob, { filename: fileName }).then((fileAsset) => {
console.log(fileAsset", fileAsset);
// you can then use the fileAsset to set and reference the file that we just uploaded to our document
client.patch("document-id-here").set({
invoice_file: {
_type: "file",
asset: {
_type: "reference",
_ref: fileAsset._id,
},
},
}).commit();
});

Error while deleting S3 objects - likely an issue with credentials but could not locate the problem

So I have been following other Q&A on stackoverflow and AWS SDK docs but I still couldn't delete S3 files with the following code, it simply gives the following error Error TypeError: Cannot read properties of undefined (reading 'byteLength')
My code (s3Client.js):
import { S3Client } from "#aws-sdk/client-s3";
const REGION = `${process.env.S3_UPLOAD_REGION}`;
const creds = {
accessKeyId: process.env.S3_UPLOAD_KEY,
secretAccessKey: process.env.S3_UPLOAD_SECRET
};
// Create an Amazon S3 service client object.
const s3Client = new S3Client({
region: REGION,
credentials: creds
});
export { s3Client };
My nextJS component:
import { DeleteObjectCommand } from "#aws-sdk/client-s3";
import { s3Client } from "../lib/s3Client.js"
const bucketParams = { Bucket: "my bucket name...", Key: "my key..." };
const run = async () => {
try {
const data = await s3Client.send(new DeleteObjectCommand(bucketParams));
console.log("Success. Object deleted.", data);
return data; // For unit tests.
} catch (err) {
console.log("Error", err);
}
};
I understand from others that this seems like a credential issue but I still couldn't wrap my head around where the problem is.
Edit:
Solution to this problem -
Check the .env, depending on whether you are using React or NextJS, you will need to have either "REACT_PUBLIC" or "NEXT_PUBLIC" in order for the environment objects to be exposed via process.env.
Check your permission in your S3 bucket and set "AllowedOrigins" to include your localhost. I had mine set as "AllowedOrigins": "*" but it wasn't enough. So I have included http://localhost:3000 and it worked.
So it looks like that you're using keys credentials for this. To debug your problem the 1st thing you should do is to check the credentials outside code and SDK and make sure they're fine.
To do this, setup the credentials on CLI by setting environment variables.
export AWS_ACCESS_KEY_ID=<Your Key here>
export AWS_SECRET_ACCESS_KEY=<SECRET HERE>
export AWS_DEFAULT_REGION=<AWS REGION>
Reference: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html
After this run this command to verify credentials are setup correctly aws sts get-caller-identity
If they show you the account and user details. Then delete the file using CLI. If that works then it is confirmed that issue is not with the credentials and with code. If not, it will point you in the right direction where the issue is.

Writing to files using File System Access API fails in Electron + Create React App

I have a create-react-app that reads and writes local files using File System Access API. When run in a browser (Chrome or Edge that support it), both reading and writing files work fine.
When the app is run in Electron, reading works but writing fails due to: Uncaught (in promise) DOMException: The request is not allowed by the user agent or the platform in the current context.
I am using the latest Electron (12.0.1) which uses the same Chromium (89.0.4389.82) as the one in my Chrome browser.
Below is the relevant code. The console log after requestPermission call shows true and granted in the browser and true and denied in Electron.
I tried disabling webSecurity when creating BrowserWindow, disabling sandbox with appendSwitch but nothing helped.
Is there a way to give Chromium in Electron more permissions?
If not, I am willing to handle file writing differently when in Electron. In that case, what to write in place of TODO in the code? Note that because it is a create-react-app, the fs module is not available.
export async function chooseAndReadFile() {
const fileHandle = await window.showOpenFilePicker().then((handles) => handles[0])
const file = await fileHandle.getFile()
const contents = await file.text()
return contents
}
export async function chooseAndWriteToFile(contents: string) {
const fileHandle = await window.showSaveFilePicker()
const descriptor: FileSystemHandlePermissionDescriptor = {
writable: true,
mode: "readwrite"
}
const permissionState = await fileHandle.requestPermission(descriptor)
console.log(window.isSecureContext)
console.log(permissionState)
const writable = await fileHandle.createWritable()
await writable.write(contents)
await writable.close()
}
let isElectron = require("is-electron")
export async function chooseAndWriteToFileUniversal(contents: string) {
if (isElectron()) {
// TODO: Do what???
} else {
chooseAndWriteToFile(contents)
}
}
Answering my own question, I finally used a solution with HTML download attribute, nicely described here. When this technique is used in Electron, it presents a file save dialog which is exactly what I want. When used in a browser, this technique just downloads the file without a prompt, so I will continue using File System Access API for browser environments.
Here is the code that handles downloading when running in Electron.
function download(filename: string, contents: string) {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(contents));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
let isElectron = require("is-electron");
export async function chooseAndWriteToFileUniversal(contents: string) {
if (isElectron()) {
download("data.txt", contents)
} else {
chooseAndWriteToFile(contents) // See the original question for implementation of this function
}
}
Still, would be nice to know why/how is Chromium in Electron more restricted than in a normal Chrome or Edge browser, and if it can be changed.

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

Get a .js file dynamically rather than importing them

I have a small app that gives support to 30+ languages. I used react-intl to achieve my task. In react-intl I got to import every locale where every local file is around 7-8kbs, whereas I want to reduce these unnecessary imports and want to import only one file
app.js
import {IntlProvider, addLocaleData} from 'react-intl'
import ca from 'react-intl/locale-data/ca'
import cs from 'react-intl/locale-data/cs'
...
import hu from 'react-intl/locale-data/hu'
import id from 'react-intl/locale-data/id'
import enMessages from '../assets/translations/en.json'
Translations.getLocale('fr').then(function(localeData){
addLocaleData(localeData);
console.log("localeData");
console.log(localeData); //Code instead of array of objects
}, function(status) {
alert('Something went wrong.');
});
Now the ca, cs,hu etc. contain array of objects returned from the respective js files.
I tried using XHR but instead of returning the array of objects, I get the code that is written in the .js file. Is there any way I can dynamically import the js file or if I can get the array of objects from the code returned by XMLHttpRequest.
Translations.js
getLocale: function(lang, successHandler, errorHandler){
var url = 'http://localhost/img/' + lang + '.js';
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('get', url, true);
//xhr.responseType = 'application/javascript';
xhr.onload = function() {
var status = xhr.status;
if (status == 200) {
resolve(xhr.response);
} else {
reject(status);
}
};
xhr.send();
});
//return message;
}
If I understand you correctly, you retrieve the javascript code, which you want retrieve the output from.
One solution is to use eval, although this is generally not considered very secure. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
You can also make the code an automatically executing function that puts the output on a global variable, and access it from there. Append the content of the js file as a script in the head tag, and make the file contain something like.
myGlobalVar = (function() {
return {
key: val
};
})();
I do not know the format of your translate.js files, but you could also consider putting the translations in a json file, if it's a fixed output for each language. Which I think would be the safest solution.
I managed to load the locale files dynamically like this :
Note that my locale string formatting might not be ideal, and ignore the polyfill if you don't plan on supporting old browsers.
import {addLocaleData} from 'react-intl';
const locale = // get this from browser language
// ensure that the polyfill is loaded before calling this
const isUsingIntlPolyfill = Object.prototype.hasOwnProperty.call(window, 'IntlPolyfill');
// eg: turns 'fr-fr' into 'fr-FR' because intl polyfill locale files are formatted like this
const formatLocale = str => `${str.split('-')[0]}${str.split('-')[1] ? `-${str.split('-')[1].toUpperCase()}` : ''}`;
if (isUsingIntlPolyfill) {
const polyfill = document.createElement('script');
// path of the file might differ for your setup
polyfill.setAttribute('src', `/i18n/polyfill/${formatLocale(locale)}.js`);
document.getElementsByTagName('head')[0].appendChild(polyfill);
}
const script = document.createElement('script');
// path of the file might differ for your setup
script.setAttribute('src', `/i18n/${locale.split('-')[0]}.js`);
script.onload = () => {
addLocaleData([...window.ReactIntlLocaleData[locale.substring(0, 2)]]);
// your locale is loaded, do some more stuff from here ...
};
document.getElementsByTagName('head')[0].appendChild(script);

Resources