Queueing offline requests to then sync to server (redux-saga) - react-native

I'm looking for the most optimal way of solving the following problem and would really appreciate some help. I have built a react native app which uses redux-saga for state management and redux-persist to keep offline data.
Problem:
When offline, users need to be able to use the native app to create "Posts" (for the sake of example) which can have multiple images attached to them. Then once they have internet connection they need to be able to click a sync/upload button. This button changes state.offlineQueue.offlineQueueShoud from 'cache' to 'sync'
Few things to consider; server side, I have a posts table, and an images table with a many to one relationship (many images to one post), which means to upload an image to the server I need to pass the post_id; meaning I have to have uploaded the post it's 'attached' to to the server so I can pass the post_id in the request. To solve this part, I have a function that uses temporary uuid's to maintain the relationship between posts and images:
function* postProcessToOfflineQueueSaga (action) {
try {
let post = Object.assign({}, action.payload);
post.uuid = uuid();
post.uploaded = false;
const imagesArr = post.images;
imagesArr.forEach(image => {
image.uuid = uuid();
image.postUuid = post.uuid;
});
post.imageIds = imagesArr.map(image => image.uuid);
delete post.images;
yield put({ type: POST_ADD_TO_OFFLINE_QUEUE, post });
yield put({ type: IMAGE_ADD_TO_OFFLINE_QUEUE, imagesArr });
} catch (error) {
console.log(error)
}
}
Which then saves it to the app state into two arrays; posts and images. E.g.
state.offlineQueue.offlinePosts: [
...
{
id: undefined,
uuid: "4df64ee5-29ba-4347-ae30-ee3d5602eae4",
imageIds: [
"62e11ef4-01f7-4052-a3e4-6cd109392818",
"2c7065ef-fed9-457f-950a-08d4224516dd",
],
uploaded: false,
}
...
]
state.offlineQueue.offlineImages: [
...
{
id: undefined,
uuid: "62e11ef4-01f7-4052-a3e4-6cd109392818",
postId: undefined,
postUuid: "4df64ee5-29ba-4347-ae30-ee3d5602eae4",
uploaded: false,
uri: "/path/to/file/on/device",
},
{
id: undefined,
uuid: "2c7065ef-fed9-457f-950a-08d4224516dd",
postId: undefined,
postUuid: "4df64ee5-29ba-4347-ae30-ee3d5602eae4",
uploaded: false,
uri: "/path/to/file/on/device",
},
...
]
state.offlineQueue.offlineQueueShoud: 'cache' // either 'cache' or 'sync'
So how do I go about sending this data to the server? What I'm thinking is:
Check 'offlineQueueShoud' (if 'cache' don't do anything to save battery?)
If offlineQueueShoud === 'sync', first iterate through the posts array and send each post to the server and use the response so that we can set post.id and image.postId to the server generated id.
Once all posts uploaded, start uploading images one by one as we now have the right post id.
Once all posts and images uploaded, clear the list.
I've had a look at redux saga channels, is that the best way to do it? If so, how do I make it so that if offlineQueueShoud === 'cache' it doesn't eat up battery, and if the app is closed/reopened no data is lost. Apologies if I'm being a noob, I'm only young :)
P.S. am I even doing this all the right way?!

Related

Mongoose creates empty document with next.js

I'm struggling with what should be a simple case, but I can't seem to get to the bottom of it: I am building a very simple CRUD app with nextjs using MongoDB.
I am following the official nextjs "with-mongodb-mongoose" as a guide (https://github.com/vercel/next.js/tree/canary/examples/with-mongodb-mongoose)
While all should be simple, my code fails to properly create a document with the form data to be sent to MongoDB. In other words, all my entries in MongoDB are empty documents:
{ _id: new ObjectId("63a5aa1317a618f4d3eefab8"), __v: 0 }
Taking it to step by step:
I have created a very simple model that only takes "name" as a paremeter:
import mongoose from 'mongoose'
const raceresultSchema = new mongoose.Schema({
name: String,
});
const Raceresult = mongoose.models.Raceresult || mongoose.model('Raceresult', raceresultSchema)
export default Raceresult
My POSt API route is exactly the same as the one in the GitHub repo from nextjs:
case 'POST':
try {
const newResultDoc = await Raceresult.create(req.body);
res.status(201).json({ success: true, data: newResultDoc })
} catch (error) {
res.status(400).json({ success: false })
}
break
default:
res.status(400).json({ success: false })
break
}
I use ThunderClient in VSCode to simplify, and I make a POST request to the right API route with the following body:
{
"name": "test"
}
I systematically get the following answer:
{
"success": true,
"data": {
"_id": "63a5b71917a618f4d3eefae3",
"__v": 0
}
}
As I'm following the syntax from the Github repo, I don't see what I'm doing wrong.
The DB connection works fine. These empty documents get saved on my MongoDB, with no problem.
When I add elements manually in MongoDB Atlas, that works fine and the items get saved properly, with no problem.
The problem happens whenever I try to use the API route to save a document. So I guess the problem must come from the API POST code or from the body format. However, I can't figure out what is wrong as I'm following the syntax from the official GitHub repo...
I have seen another StackOverflow post with a similar type of issue, but the solution did not seem applicable to my case.
Appreciate your help!
EDIT: I copy/pastd the code from the nextjs github repo (Pet model, and Pet API route) and made a POST request in Thunder Client to save a pet in my database (which is not the point) and... it worked, the document is saved in my database with the data from the req body... I'll keep trying and update this post if I succeed with my own model
So I seem to have solved it like this:
Connected to my MongoDB database in my terminal and dropped all existing collections
stopped the app and restarted running npm run dev
tried the API calls again and... it now works.
I guess it had something to do with saved Schema Types that were not being validated somewhere... I don't know more, but this now works.

react-native how to remove persistence on firebase snapshot

I am little confused.
I am listening to firebase snapshot with sample code below
unsubscribe = firebase
.firestore()
.collection('collection')
.doc(id)
.onSnapshot(
function(doc) {
// other code
},
);
This will listen to the collection if there's new item for the specific id.
Then, closing the app will unsubscribe to the snapshot
useEffect(() => {
return () => {
if (unsubscribe) {
unsubscribe()
}
}
}, []);
It is working fine.
However, given the scenario.
If the snapshot triggered (eg. { value: 1 }) and then I closed the app.
Removed the value on the firebase for the specific id. (meaning the id should not received the item)
Re-open the app
I still get the previous value which is { value: 1} and then get the newest value which is undefined (since i removed the value)
Is the value persists on the app? How can I remove this one upon re-opening of the app?
Thanks!
From this answer:
There is now a feature in the API for clearing persistence. It is not recommended for anything but tests, but you can use
firebase.firestore().clearPersistence().catch(error => {
console.error('Could not enable persistence:', error.code);
})
It must run before the Firestore database is used.

How to minimize data traffic in vuejs

At work, we think about using Vuejs with Vuex for our frontend. I have already used it for private projects.
One of our big questions is the traffic. Our platform needs a lot of data, sometimes in large packages. Some of them are really big and it would be more expensive to load them more than once.
I've seen many examples of vue with vuex, but for me it looked like all the samples would request the same (already loaded) data every time they paged.
My real question is: Is there a way in vuejs or general to avoid or solve this problem? The only helpful thing I have found so far was this.
As far as I know, you can use this library https://github.com/kuitos/axios-extensions
An example here
import Axios from 'Axios';
import { throttleAdapterEnhancer } from 'axios-extensions';
const http = axios.create({
baseURL: '/',
headers: { 'Cache-Control': 'no-cache' },
adapter: throttleAdapterEnhancer(axios.defaults.adapter, { threshold: 2 * 1000 })
});
http.get('/users'); // make real http request
http.get('/users'); // responsed from the cache
http.get('/users'); // responsed from the cache
setTimeout(() => {
http.get('/users'); // after 2s, the real request makes again
}, 2 * 1000)
As you can see, you can create an adaptor and custom what you want. For example here, you keep the cache for only 2 seconds. So the first request to /users is a real one. The second and thirst request are cache, and the last one is after the two seconds, so it's a real one.
Can you be more specific about the context of how you will keep the cache. When do you need to reload the cache, after how many times, after which event, ...?
The strategy I use is to store the value on the Vuex state.
I write all my request in Vuex actions. In every action, I check if the data already exists on the Vuex state. If it does, I simply bypass the request and return the data from the state (saving requests from being called multiple times). If it doesn't exist, I'll make the request, then store the result on the Vuex state, and return the value.
Vuex Action:
getLists({ state, commit }) {
return new Promise((resolve, reject) => {
if (state.isSetLists === false) {
getListsFromServer((error, data) => {
if (error) {
reject(error);
} else {
console.log('call to getLists successful:', data);
commit('setLists', data.lists);
resolve(data.lists);
}
});
} else {
resolve(state.lists);
}
});
},
Then, the setLists mutation handles it like so:
setLists(state, lists) {
state.isSetLists = true;
state.lists = lists;
},
This way, the user can page around all they want, and only ever call each request once.

Upload images with apollo-upload-client in React Native

I'm trying out Prisma and React Native right now. Currently I'm trying to upload images to my db with the package _apollo-upload-client (https://github.com/jaydenseric/apollo-upload-client). But it's not going so well.
Currently I can select an image with the ImagePicker from Expo. And then I'm trying to do my mutation with the Apollo Client:
await this.props.mutate({
variables: {
name,
description,
price,
image,
},
});
But I get the following error:
Network error: JSON Parse error: Unexpected identifier "POST"
- node_modules/apollo-client/bundle.umd.js:76:32 in ApolloError
- node_modules/apollo-client/bundle.umd.js:797:43 in error
And I believe it's from these lines of code:
const image = new ReactNativeFile({
uri: imageUrl,
type: 'image/png',
name: 'i-am-a-name',
});
Which is almost identical from the their example, https://github.com/jaydenseric/apollo-upload-client#react-native.
imageUrl is from my state. And when I console.log image I get the following:
ReactNativeFile {
"name": "i-am-a-name",
"type": "image/png",
"uri": "file:///Users/martinnord/Library/Developer/CoreSimulator/Devices/4C297288-A876-4159-9CD7-41D75303D07F/data/Containers/Data/Application/8E899238-DE52-47BF-99E2-583717740E40/Library/Caches/ExponentExperienceData/%2540anonymous%252Fecommerce-app-e5eacce4-b22c-4ab9-9151-55cd82ba58bf/ImagePicker/771798A4-84F1-4130-AB37-9F382546AE47.png",
}
So something is popping out. But I can't get any further and I'm hoping I could get some tips from someone.
I also didn't include any code from the backend since I believe the problem lays on the frontend. But if anyone would like to take a look at the backend I can update the question, or you could take a look here: https://github.com/Martinnord/Ecommerce-server/tree/image_uploads.
Thanks a lot for reading! Cheers.
Update
After someone asked after the logic in the server I have decided to past it below:
Product.ts
// import shortid from 'shortid'
import { createWriteStream } from 'fs'
import { getUserId, Context } from '../../utils'
const storeUpload = async ({ stream, filename }): Promise<any> => {
// const path = `images/${shortid.generate()}`
const path = `images/test`
return new Promise((resolve, reject) =>
stream
.pipe(createWriteStream(path))
.on('finish', () => resolve({ path }))
.on('error', reject),
)
}
const processUpload = async upload => {
const { stream, filename, mimetype, encoding } = await upload
const { path } = await storeUpload({ stream, filename })
return path
}
export const product = {
async createProduct(parent, { name, description, price, image }, ctx: Context, info) {
// const userId = getUserId(ctx)
const userId = 1;
console.log(image);
const imageUrl = await processUpload(image);
console.log(imageUrl);
return ctx.db.mutation.createProduct(
{
data: {
name,
description,
price,
imageUrl,
seller: {
connect: { id: userId },
},
},
},
info
)
},
}
Solution has been found.
I am a little embarrassed that this was the problem that I faced and I don't know if I should even accept this answer because of awkward I felt when I fixed the issue. But....
There was nothing wrong with my code, but there was a problem with the dependencies versions. I tried to backtrack everything on my app, so I decided to start from the beginning and create a new account. I expected it to work just fine, but I got this error:
Error: Cannot use GraphQLNonNull "User!" from another module or realm.
Ensure that there is only one instance of "graphql" in the node_modules
directory. If different versions of "graphql" are the dependencies of other
relied on modules, use "resolutions" to ensure only one version is installed.
https://yarnpkg.com/en/docs/selective-version-resolutions
Duplicate "graphql" modules cannot be used at the same time since different
versions may have different capabilities and behavior. The data from one
version used in the function from another could produce confusing and
spurious results.
Then I understand that something (that I didn't think of) was wrong. I checked my dependencies versions and compared them with Graphcool's example, https://github.com/graphcool/graphql-server-example/blob/master/package.json. And I noticed that my dependencies was outdated. So I upgraded them and everything worked! So that was what I had to do. Update my dependencies.
Moral of the story
Always, always check your damn dependencies versions...
Crawling through your code, I have found this repository, which must be the front-end code if I am not mistaken?
As you've mentioned, apollo-upload-server requires some additional set-up and same goes for the front-end part of your project. You can find more about it here.
As far as I know, the problematic part of your code must be the initialisation of the Apollo Client. From my observation, you've put everything Apollo requires inside of src/index folder, but haven't included Apollo Upload Client itself.
I have created a gist from one of my projects which initialises Apollo Upload Client alongside some other things, but I think you'll find yourself out.
https://gist.github.com/maticzav/86892448682f40e0bc9fc4d4a3acd93a
Hope this helps you! 🙂

Element UI - Upload component not waiting for confirmation

I'm using the last version of vue-js and element-ui
This question addresses the upload component whose documentation can be found here
Situation
A hook function before-upload allows us to check some stuff before allowing the upload to be accomplished.
Problem
When using a confirmation dialog, the upload does not wait the user to be accomplished
Question
Am I doing something wrong? Is this a bug?
https://jsfiddle.net/buoy2m4o/1/
You need to return the promise.
beforeAvatarUpload(file) {
return this.$confirm('Are you sure to upload this?', 'Warning', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
// do stuff
})
}
Updated fiddle.