How to save imported JSON file with Expo Filesystem - json

I have been working on a React Native project with Expo that uses a json file to store local data. I am importing the data like so
import data from '../database.json'
I am making changes (adding and removing) to the imported JSON by using data.push(new_data). These changes are not persistent when I close the app because I cannot figure out how to save them. I have looked at using the expo-file-system library as so:
import * as FileSystem from 'expo-file-system';
...
FileSystem.writeAsStringAsync(FileSystem.documentDirectory + 'database.json', data);
This is from looking at examples in the API documentations. This however always throws promise rejections and doesn't end up writing the file. Can you point me in the right direction?
Also, should I import the database.json in a different way so I will already have the uri to save it to?

The documentation doesn't give an example of it's returned props in promises, so I was overlooking it for longer than I care to admit 😅. I was really dedicated to figuring this out so I could use the Expo solution, and totally missed the return Promise for createFileAsync, so hopefully this saves someone a significant amount of time in the future.
import * as FileSystem from 'expo-file-system';
const { StorageAccessFramework } = FileSystem;
const saveFile = async () => {
const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync();
// Check if permission granted
if (permissions.granted) {
// Get the directory uri that was approved
let directoryUri = permissions.directoryUri;
let data = "Hello World";
// Create file and pass it's SAF URI
await StorageAccessFramework.createFileAsync(directoryUri, "filename", "application/json").then(async(fileUri) => {
// Save data to newly created file
await FileSystem.writeAsStringAsync(fileUri, data, { encoding: FileSystem.EncodingType.UTF8 });
})
.catch((e) => {
console.log(e);
});
} else {
alert("You must allow permission to save.")
}
}

Use AsyncStorage instead. The react native package is deprecated but working, or use #react-native-community/async-storage and convert json to string (AsyncStorage can only store strings)
Set item
import AsyncStorage from '#react-native-community/async-storage';
...
await AsyncStorage.setItem('myData', JSON.stringify(data))
Get item
const data = await AsyncStorage.getItem('myData')

I found #JayMax answer very helpful however it's only for Android.
On iOS all you need to do is use Sharing.shareAsync and then you can save data to the file. Check this example:
const fileUri = FileSystem.documentDirectory + 'data.txt';
FileSystem.writeAsStringAsync(fileUri, 'here goes your data from JSON. You can stringify it :)', {
encoding: FileSystem.EncodingType.UTF8,
});
const UTI = 'public.text';
Sharing.shareAsync(fileUri, {UTI}).catch((error) => {
console.log(error);
});

If you using AsyncStorage, it only store for small data. Maybe 6mb or 10 mb.
You can use expo fileSystem
import * as FileSystem from 'expo-file-system';
...
FileSystem.writeAsStringAsync(FileSystem.documentDirectory + 'database.json', data);
Convert your data (Type json to string) Such as this:
writeData = async () => {
var persons = ''
await axios.get(`http://192.168.0.48:4000/api/sql/student`)
.then(res => {
persons = res.data
})
await FileSystem.writeAsStringAsync(FileSystem.documentDirectory + `offline_queue_stored.json`, JSON.stringify(persons));
}

#1.If the JSON File is in your Project Folder (PC/Laptop)
import data from './database.json';
#2. If the JSON File is in your Phone
import * as FileSystem from 'expo-file-system';
import * as DocumentPicker from 'expo-document-picker';
this.state = {
fileURI: null,
};
componentDidMount = () =>{
this._pickDocument();
}
_pickDocument = async () => {
let result = await DocumentPicker.getDocumentAsync({});
this.setState({
fileURI: result.uri
})
let fileData = await FileSystem.readAsStringAsync(this.state.fileURI)
console.log(fileData)
};

Related

Batching with useQuery react hooks getting back undefined

I am currently working on a project which requires me to make multiple queries/mutations. I tried setting up my apollo client with BatchHttpLink and I can see the data I am requesting in the network tab in the browser. It is coming back at an array of objects instead of JSON.
But the issue is when I try to grab the data in my component data is undefined. I tried using HttpLink instead of BatchHttpLink and I can get the data back from the hook.
My suspicion is the shape of the object that comes back from the response is different, I tried looking into documentation but I can't find much about batching.
Currently using "#apollo/client#^3.0.2"
Here's my client set up.
import { ApolloClient, InMemoryCache, ApolloLink, from } from '#apollo/client'
import { BatchHttpLink } from '#apollo/client/link/batch-http'
import { onError } from '#apollo/client/link/error'
const BASE_URL = 'http://localhost:4000'
const httpLink = new BatchHttpLink({
uri: BASE_URL,
credentials: 'include',
})
const csrfMiddleware = new ApolloLink((operation, forward) => {
operation.setContext(({ headers = {} }) => ({
headers: {
...headers,
'X-CSRF-Token': getCSRFToken(),
},
}))
return forward(operation)
})
const errorMiddleware = onError(({ networkError }) => {
if (networkError && 'statusCode' in networkError && networkError.statusCode === 401) {
window.location.assign('/accounts/login')
}
})
const client = new ApolloClient({
link: from([errorMiddleware, csrfMiddleware, httpLink]),
cache: new InMemoryCache(),
})
This is the react hook I'm trying to console log.
const {data} = useQuery(GET_USER_PERMISSIONS_AND_PREFERENCES)
Figured it out. You need to add another middleware to return the data that the useQuery hook can recognize. The data that comes back in the batch call is an array of objects shaped
{
payload: {
data: { ... }
}
}
So something like this did the trick for me
const batchParseMiddleware = new ApolloLink((operation, forward) => {
return forward(operation).map((data: any) => data.payload)
})
I have been having a similar issue, and have so far only been able to solve it by breaking batching and converting to a normal HttpLink

Importing static local json file to Vue store (exclude from build)

I'm trying to have my add data from a local static JSON file to the Vue vuex store.
I want my JSON file separated from the bundle process, so that i can change the content anytime in future, without rebuilding the whole site.
I have my json file [test.json] in the public folder
And with the following code, i managed to import the data, but its still being bundled on build of the site.
import data from '../public/test';
export const state = () => ({
allData: {}
})
export const mutations = {
SET_ALL_DATA(state, data) {
state.allData = data
}
}
export const actions = {
nuxtServerInit({ commit }) {
commit('SET_ALL_DATA', data)
}
}
I have also tried hosting the JSON file on a web server and doing an axios call to it on nuxtServerInit like so. but the called upon json file still gets bundled, as changing the hosted json file does nothing to update the content.
export const actions = {
async nuxtServerInit ({ commit }, { $axios }) {
const res = await $axios.$get('https://www.amq.fariskassim.com/testjson/test.json')
commit('SET_ALL_DATA', res)
}
}
I'm all out of solutions so if anyone can point me in the right direction, i would be totally grateful

Resolving an ES6 module imported from a URL with Rollup

It is perfectly valid to import from a URL inside an ES6 module and as such I've been using this technique to reuse modules between microservices that sit on different hosts/ports:
import { authInstance } from "http://auth-microservice/js/authInstance.js"
I'm approaching a release cycle and have started down my usual path of bundling to IIFEs using rollup. Rollup doesn't appear to support es6 module imports from URLs, I think it should as this is allowed in the spec :(
module-name
The module to import from. This is often a relative or absolute path name to the .js file containing the module. Certain bundlers may permit or require the use of the extension; check your environment. Only single quotes and double quotes Strings are allowed. (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)
I've dug through the interwebs for an hour now and have come up with nothing. Has anybody seen a resolver similar to rollup-plugin-node-resolve for resolving modules from URLs?
I had to move on from this quickly so ended up just writing a skeleton of a rollup plugin. I still feel that resolving absolute paths should be a core feature of rollup.
Updated snippet
We have been using this to transpile production code for several of our apps for a considerable amount of time now.
const fs = require('fs'),
path = require('path'),
axios = require("axios")
const createDir = path => !fs.existsSync(path) && fs.mkdirSync(path)
const mirrorDirectoryPaths = async ({ cacheLocation, url }) => {
createDir(cacheLocation)
const dirs = [], scriptPath = url.replace(/:\/\/|:/g, "-")
let currentDir = path.dirname(scriptPath)
while (currentDir !== '.') {
dirs.unshift(currentDir)
currentDir = path.dirname(currentDir)
}
dirs.forEach(d => createDir(`${cacheLocation}${d}`))
return `${cacheLocation}${scriptPath}`
}
const cacheIndex = {}
const writeToDiskCache = async ({ cacheLocation, url }) => {
//Write a file to the local disk cache for rollup to pick up.
//If the file is already existing use it instead of writing a new one.
const cached = cacheIndex[url]
if (cached) return cached
const cacheFile = await mirrorDirectoryPaths({ cacheLocation, url }),
data = (await axiosInstance.get(url).catch((e) => { console.log(url, e) })).data
fs.writeFileSync(cacheFile, data)
cacheIndex[url] = cacheFile
return cacheFile
}
const urlPlugin = (options = { cacheLocation }) => {
return {
async resolveId(importee, importer) {
//We importing from a URL
if (/^https?:\/\//.test(importee)) {
return await writeToDiskCache({ cacheLocation: options.cacheLocation, url: importee })
}
//We are importing from a file within the cacheLocation (originally from a URL) and need to continue the cache import chain.
if (importer && importer.startsWith(options.cacheLocation) && /^..?\//.test(importee)) {
const importerUrl = Object.keys(cacheIndex).find(key => cacheIndex[key] === importer),
importerPath = path.dirname(importerUrl),
importeeUrl = path.normalize(`${importerPath}/${importee}`).replace(":\\", "://").replace(/\\/g, "/")
return await writeToDiskCache({ cacheLocation: options.cacheLocation, url: importeeUrl })
}
}
}
}
This plugin together with the following config works for me:
https://github.com/mjackson/rollup-plugin-url-resolve
import typescript from "#rollup/plugin-typescript";
import urlResolve from "rollup-plugin-url-resolve";
export default {
output: {
format: "esm",
},
plugins: [
typescript({ lib: ["es5", "es6", "dom"], target: "es5" }),
urlResolve(),
],
};
You can remove the TypeScript plugin obviously.

Understanding the streaming concept in node.js

I am trying to convert a CSV data to JSON data while streaming from a HTTP url by using "csvtojson" package.
const csv = require("csvtojson");
const request = require('request');
let options = {
uri: '',
****
};
let tempArr = [];
csv()
.fromStream(request(options))
.on("json", (jsonObj) => {
if (JSON.parse(jsonObj.Response).intents[0].intent == "None")
tempArr.push(JSON.parse(jsonObj.Response));
})
.on('done', (error) => {
callback(null, tempArr)
})
This is calling under an API. When I starts the server and call this api to convert csv to json, it works perfectly.
And If I call the same API again, the "json" event is not getting triggered, Instead "done" event is triggered directly.
i.e., the streaming is not done from the second time. why is it behaving like this?
and what should I do to solve this problem?

Convert CSV to JSON client side with React DropZone

From React dropzone, i receive a File object with a File.preview property whose value is a blob:url. i.e. File {preview: "blob:http://localhost:8080/52b6bad4-58f4-4ths-a2f5-4ee258ba864a"
Is there a way to convert this to json on the client? The file isnt need to be stored in a database (the convert JSON will be). I've attempted to use csvtojson but it's unable to use the file system as it uses node to power it. Ideally would like to convert this in the client if possible, once user has uploaded it. Any suggestions welcomed.
<Dropzone
name={field.name}
onDrop={(acceptedFiles, rejectedFiles) => {
acceptedFiles.forEach(file => {
console.log(file)
let tempFile = file.preview
csv()
.fromSteam(tempFile) // this errors with fs.exists not a function as its not running serverside
.on('end_parsed',(jsonArrObj)=>{
console.log(jsonArrObj)
})
})
}}
>
Yes, its possible with FileReader and csv:
import csv from 'csv';
// ...
const onDrop = onDrop = (e) => {
const reader = new FileReader();
reader.onload = () => {
csv.parse(reader.result, (err, data) => {
console.log(data);
});
};
reader.readAsBinaryString(e[0]);
}
// ...
<Dropzone name={field.name} onDrop={onDrop} />
FileReader API: https://developer.mozilla.org/en/docs/Web/API/FileReader
csv package: https://www.npmjs.com/package/csv