AsyncStorage before list render React Native - node.js

I have an app that list incidents of a animals charity ong.
After the login, the ong is directed to your incidents list, so i must pass your ID of login page to load this list.
I'm trying to load the variables of AsyncStorage in a function and then pass it to a callback that load the incidents list in React useEffect.
Code:
export default function Incidents() {
const [incidents, setIncidents] = useState([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [id, setID] = useState('');
const [name, setName] = useState('');
loadStorage:
loadStorage = async function(callback) {
try {
const ongid = await AsyncStorage.getItem('ongID');
const ongname = await AsyncStorage.getItem('ongName');
if (ongid && ongname !== null) {
setID(ongid);
setName(ongname);
}
} catch (e) {
alert(e);
}
callback();
}
loadIncidents:
loadIncidents = async function() {
if (loading) {
return;
}
if (total > 0 && incidents.lenght === total) {
return;
}
try {
const response = await api.get('profiles', {
headers: {
Authorization: id,
},
params: { page },
});
setIncidents([ ... incidents, ... response.data]);
setTotal(response.headers['x-total-count']);
setPage(page +1);
setLoading(false);
} catch (e) {
alert(e); //When i access the incident list page i got the error: 'Error: Request failed with status code 400'
}
}
useEffect:
useEffect(() => {
navigation.addListener('focus', () => {
loadStorage(loadIncidents);
});
}, []);
The error in the alert (e) line of loadIncidents happens because useEffect is not fully calling the loadStorage (loadIncidents) part the first time I enter the application.
I have another page called newIncident, if I navigate to it after pressing OK on this error and go back to the incident list page (by navite.goBack ()) the list will be loaded with all the incidents from the logged ONG.
Need this behavior when I first enter the page that lists the ONG's incidents. Since the both methods are asynchronous, i'm not be able to figure out how to do this.

Maybe you can consider using react-native-easy-app. After completing the initial binding, it can enable you to access the properties in AsyncStorage synchronously in the form of assignment and value, because it is simple enough, so you will not appear above Various problems.

Did it using promises. code below:
async function loadStorage() {
var ongid = '';
var ongname = '';
try {
ongid = await AsyncStorage.getItem('ongID');
ongname = await AsyncStorage.getItem('ongName');
} catch (e) {
alert(e);
}
return new Promise((resolve, reject) => {
if (ongid !== null) {
setID(ongid);
setName(ongname);
const ong = { 'name': ongname, 'id': ongid}
return resolve(ong);
}
})
}
Then in useEffect information loads like this:
loadStorage()
.then(loadIncidents)

Related

Push Data to MongoDB on Google SignIn Firebase

I wanted to write a method where onClick the google sign in starts and after successful sign in it makes a post request to my API.But the weird problem is 30% of the times the sign in data doesnt come to mongo db.I even called signout function in the catch block.Please help if someone notice any error!!
const Hero = () => {
const [user, setUser] = useState(null);
const [fetchUser, setFetchUser] = useState(null);
const handleGoogleSignIn = () => {
const googleProvider = new GoogleAuthProvider();
signInWithPopup(auth, googleProvider)
.then(async (result) => {
console.log(result);
try {
const { data } = await axios.post(
"https://myAPIherokuapp.com/api/v1/9c142e80023e07c3/registerUser",
{ name: result.user.displayName, email: result.user.email }
);
console.log(data);
} catch (err) {
console.log(err);
signOut(auth)
}
})
.catch((error) => {
console.log(error);
});
};
Maybe try async/await at the handleGoogleSignIn level? e.g.
const handleGoogleSignIn = async () => {
const googleProvider = await new GoogleAuthProvider();
const userResult = await signInWithPopup(auth, googleProvier);
await axios.post('url', userResult);
...
}
I think that should help?

Async function to scrape subreddits using Cheerio returns undefined

The script by itself works great (entering the url manually, writing a json file using the fs module, node script_name.js) but within a Express get request it returns undefined.
So I've built a simple frontend to let the user enter the subreddit name to be scraped.
And here's where the problem is:
Express controller
const run = require("../run");
requestPosts: async (req, res) => {
try {
const { subreddit } = req.body;
const response = await run(subreddit);
//console.log(response);
res.json(response);
} catch (error) {
console.error(error);
}
},
Cheerio functions
const axios = require("axios");
const { load } = require("cheerio");
let posts = [];
async function getImage(postLink) {
const { data } = await axios(postLink);
const $ = load(data);
return $("a.post-link").attr("href");
}
async function run(url) {
try {
console.log(url);
const { data } = await axios(url);
const $ = load(data);
$(".thing.linkflair.link").map(async (i, e) => {
const title = $(e)
.find(".entry.unvoted .top-matter .title .title")
.text();
const user = $(e)
.find(".entry.unvoted .top-matter .tagline .author")
.text();
const profileLink = `https://old.reddit.com/user/${user}`;
const postLink = `https://old.reddit.com/${$(e).find("a").attr("href")}`;
// const thumbail = $(e).find("a img").attr("src");
const image = await getImage(postLink);
posts.push({
id: i + 1,
title,
postLink,
image,
user: { user, profileLink },
});
});
const nextPage = $(".next-button a").attr("href");
if (nextPage) {
await run(nextPage);
} else {
return posts;
}
} catch (error) {
console.error(error);
}
}
module.exports = run;
I've tried working with Promise((resolve, reject) => {}).
I think it's returning undefined because maybe the code its not synchronized.
(idk if it makes sense, i've just started programming)
.map() is not promise-aware and does not wait for your promises to finish. So, $(".thing.linkflair.link").map() finishes long before any of the asynchronous functions inside its callback do. Thus you try to return posts BEFORE it has been populated.
Passing an async callback to .map() will return an array of promises. You can use Promise.all() on those promises to know when they are done and once you're doing that, you may as well just return each post object rather that using a higher level scoped/shared object, thus making the code more self contained.
I would suggest this code:
async function run(url) {
try {
console.log(url);
const { data } = await axios(url);
const $ = load(data);
const posts = await Promise.all($(".thing.linkflair.link").map(async (i, e) => {
const title = $(e)
.find(".entry.unvoted .top-matter .title .title")
.text();
const user = $(e)
.find(".entry.unvoted .top-matter .tagline .author")
.text();
const profileLink = `https://old.reddit.com/user/${user}`;
const postLink = `https://old.reddit.com/${$(e).find("a").attr("href")}`;
// const thumbail = $(e).find("a img").attr("src");
const image = await getImage(postLink);
// return a post object
return {
id: i + 1,
title,
postLink,
image,
user: { user, profileLink },
};
}));
const nextPage = $(".next-button a").attr("href");
if (nextPage) {
const newPosts = await run(nextPage);
// add these posts to the ones we already have
posts.push(...newPosts);
}
return posts;
} catch (error) {
console.error(error);
}
}

Path `comment` is required. MERN stack

I don't understand why I get this error. This is my controller:
export const createProductReview = async (req, res) => {
const { rating, comment } = req.body;
const product = await Product.findById(req.params.id);
if (product) {
const alreadyReviewed = product.reviews.find(
r => r.user.toString() === req.user.userId.toString()
);
if (alreadyReviewed) {
throw new NotFoundError('Product already reviewed');
}
const review = {
user: req.user.userId,
name: req.user.username,
rating: Number(rating),
comment,
};
product.reviews.push(review);
product.numOfReviews = product.reviews.length;
product.rating =
product.reviews.reduce((acc, item) => item.rating + acc, 0) /
product.reviews.length;
await product.save();
res.status(StatusCodes.OK).json({ message: 'Review added', review });
} else {
throw new NotFoundError('Product not found');
}
};
This is mine productPage where i dispatch addProductReview and passing product id from params and review object:
const [rating, setRating] = useState(0);
const [comment, setComment] = useState('');
const submitHandler = e => {
e.preventDefault();
dispatch(
addProductReview(id, {
rating,
comment,
})
);
};
And this is my productSlice:
export const addProductReview = createAsyncThunk(
'product/review',
async (id, { review }, thunkAPI) => {
try {
const { data } = await axios.post(
`/api/v1/products/${id}/reviews`,
review
);
return data;
} catch (error) {
const message = error.response.data.msg;
return thunkAPI.rejectWithValue(message);
}
}
);
I have no clue why i got error Path comment is required. i pass review object to route.
The issue is with the parameters used in your Thunk payloadCreator. From the documentation...
The payloadCreator function will be called with two arguments:
arg: a single value, containing the first parameter that was passed to the thunk action creator when it was dispatched. This is useful for passing in values like item IDs that may be needed as part of the request. If you need to pass in multiple values, pass them together in an object when you dispatch the thunk, like dispatch(fetchUsers({status: 'active', sortBy: 'name'})).
thunkAPI: an object containing all of the parameters that are normally passed to a Redux thunk function, as well as additional options
Your payloadCreator has three arguments which is incorrect.
Try this instead
export const addProductReview = createAsyncThunk(
'product/review',
async ({ id, ...review }, thunkAPI) => {
try {
const { data } = await axios.post(
`/api/v1/products/${id}/reviews`,
review
);
return data;
} catch (error) {
const message = error.response.data.msg;
return thunkAPI.rejectWithValue(message);
}
}
);
and dispatch it like this
dispatch(addProductReview({ id, rating, comment }));

Reduce Auth Requests

I am making a few node.js scripts using google-api-nodejs-client.
Here is the basic auth request to interact with the api:
const { google } = require("googleapis");
const auth = new google.auth.GoogleAuth({
keyFile: "credentials.json",
scopes: "https://www.googleapis.com/auth/spreadsheets",
});
const getAuthClient = async () => {
try {
return await auth.getClient();
} catch (error) {
console.error(error);
}
};
const sheetsClient = async () => {
const client = await getAuthClient();
return await google.sheets({ version: "v4", auth: client });
};
module.exports = { sheetsClient };
Now, whenever I create a function that needs to use the sheetsClient I need to set it up like this (the examples below are generic examples, I will have other calls to the api where I'll need to get the sheets client. In some cases I'll need to read (get the client) and the write (get the client again) in different functions called one after the other:
const { google } = require("googleapis");
const { sheetsClient } = require("./googleAuth");
const createSheet = async (name) => {
const client = await sheetsClient();
const sheet = await client.spreadsheets.create({
resource: {
properties: {
title,
},
},
});
};
const updateSheet = async (name) => {
const client = await sheetsClient();
const sheet = await client.spreadsheets.update({
resource: {
properties: {
title,
},
},
});
};
const deleteSheet = async (name) => {
const client = await sheetsClient();
const sheet = await client.spreadsheets.delete({
resource: {
properties: {
title,
},
},
});
};
Is there a better way to get access to the client without having to call it everytime within a function?
there are many possibilities.
the easiest may be to call this only once, outside of all functions.
const { google } = require("googleapis");
const { sheetsClient } = require("./googleAuth");
// globally defined
const client = ( async () => await sheetsClient())();
// rest of code
const createSheet = async (name) => {
// deleted : const client = await sheetsClient();
const sheet = await client.spreadsheets.create({
resource: {
properties: {
title,
},
},
});
};
this will create a global client variable in this js file.
then you can remove its declaration from every function.
the code will still run smoothly but there will be only one authentication.
Another way to deal with your problem is to assure that the auth function really is executed only once by using a flag. (this solution is related to memoization)
var client = null;
const getAuthClient = async () => {
if (client) return client;
try {
client = await auth.getClient();
return client;
} catch (error) {
console.error(error);
}
};

Unable to make use of links to fetch different titles

I've created a script in node using promise in combination with request and cheerio to parse the links under Province column from this webpage then reuse those links to scrape all the urls under Office column from all of such pages and finally make use these links to collect the title from all of such target pages, as in Cairos main Post Office in this page.
My current script most of the times gets stuck. However, sometimes it throws this error UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'parent' of undefined. I've checked each of the functions and found that they are all working in the right way individually.
Although the script looks a bit bigger, it is built upon a very simple logic which is make use of each links from it's landing page until it reaches the title of it's target page.
This is my try so far:
const request = require('request');
const cheerio = require('cheerio');
const link = 'https://www.egyptcodebase.com/en/p/all';
const base_link = 'https://www.egyptcodebase.com/en/';
const items = [];
const nitems = [];
let getLinks = () => {
return new Promise((resolve, reject) => {
request(link, function(error, response, html) {
let $ = cheerio.load(html);
if (error) return reject(error);
try {
$('.table tbody tr').each(function() {
items.push(base_link + $(this).find("a").attr("href"));
});
resolve(items);
} catch (e) {
reject(e);
}
});
});
};
let getData = (links) => {
const promises = links
.map(nurl => new Promise((resolve, reject) => {
request(nurl, function(error, response, html) {
let $ = cheerio.load(html);
if (error) return reject(error);
try {
$('.table tbody tr').each(function() {
nitems.push(base_link + $(this).find("a").attr("href"));
});
resolve(nitems);
} catch (e) {
reject(e);
}
})
}))
return Promise.all(promises)
}
let FetchData = (links) => {
const promises = links
.map(nurl => new Promise((resolve, reject) => {
request(nurl, function(error, response, html) {
let $ = cheerio.load(html);
if (error) return reject(error);
try {
resolve($(".home-title > h2").eq(0).text());
} catch (e) {
reject(e);
}
})
}))
return Promise.all(promises)
}
getLinks().then(resultList => {
getData(resultList).then(resultSet => {
FetchData(resultSet).then(title =>{
console.log(title);
})
})
})
How can I scrape the titles from target pages making use of all the links from landing pages?
It would be much easier to ask the website Owner about the data which you need.
He might understand your request and give it to you for free, instead of scraping his site.
P.S: I was surprised to find a question about how to scrape my own website.
P.S2: If you just need all post office titles I could have given it for you for free :D
P.S3: Your error is maybe happening because of some time the page doesn't have the element which you are trying to parse using cheerio.
So the issue is with 2D array. If you go through carefully over your getData function, you're returning 2D array.
map return an array and within that map you're resolving another array nitems.
Here's the working code:
const base_link = 'https://www.egyptcodebase.com/en/';
// helper wrapper DRY
const getHtmls = (url) => {
return new Promise((resolve, reject) => {
request({ uri: url, method: 'GET', followAllRedirects: true } , function(error, response, html) {
if (error) reject(error);
else resolve(html);
});
})
}
let getLinks = async () => {
const link = 'https://www.egyptcodebase.com/en/p/all';
const items = [];
try {
const html = await getHtmls(link);
let $ = cheerio.load(html);
$('.table tbody tr').each(function() {
items.push(base_link + $(this).find("a").attr("href"));
});
} catch (e) {
// handling error here so execution can continue for good eggs
console.error(e.message)
}
return items;
};
let getData = async (links) => {
const out = [];
try {
const promises = links.map(nurl => getHtmls(nurl));
const htmls = await Promise.all(promises);
htmls.forEach(html => {
let $ = cheerio.load(html);
$('.table tbody tr').each(function() {
out.push(base_link + $(this).find("a").attr("href"));
});
})
} catch (e) {
// handling error here so execution can continue for good eggs
console.error(e.message)
}
return out;
}
let FetchData = async (links) => {
const out = [];
try {
const promises = links.map(nurl => getHtmls(nurl));
const htmls = await Promise.all(promises)
htmls.forEach(html => {
try {
let $ = cheerio.load(html);
out.push($(".home-title > h2").eq(0).text());
} catch (e){
// handling error here so execution can continue for good eggs
console.error(e.message)
}
})
} catch (e) {
// handling error here so execution can continue for good eggs
console.error(e.message)
}
return out;
}
getLinks().then(resultList => {
getData(resultList).then(resultSet => {
FetchData(resultSet).then(title =>{
console.log(title);
})
})
})
Note: Instead of writing your own Promise wrapper, you could use request-promise package
Issue with your code is in FetchData function, as in that function you are passing links and then using map over it.
But if you look inside that map function and check the value of 'nurl' variable it will be an array of links and its data type would be object.
According to the semantics of request function, its first param should be string, so if you iterate over the 'nurl' variable to get the values, then it would work.
My code snippet for one url from array

Resources