Infinite loop on ComponentWillMount ReactJS - javascript

I'm trying to use ReactJS on Salesforce but the queries are quite problematic
Take a look at this code, trying to make something cool to get all data but the while isn't stop, don't know why, please help me and feel free to ask me anything
componentWillMount(){
let queryMore = true
let offSet = 0
let contents = []
while(queryMore) {
makeDeferredProvider()
let contentObject = SObjectModel.deferredObject('Content')
let retrieveOptions = {
limit: 10,
where: {
Type__c: { eq: 'Image' }
}
}
offSet > 0 ? retrieveOptions.offset = offSet : ''
let contentPromise = contentObject.retrieve(retrieveOptions)
contentPromise.then( records => {
records.forEach( record => {
let item = {
'name': record.get('Name'),
'id': record.get('Id')
}
contents.push(item)
})
records.length = 10 ? offSet += 10 : queryMore = false
}, error => {
console.log(error)
})
}
this.setState({
contents: contents
})
}

Async React will make this easier in the future, but right now you need to not block rendering while your promise is resolving.
Here's a high-level solution:
Stop using ComponentWillMount. It is being deprecated.
Stop using the while loop. It won't work with your promise. Get it working first then worry about queryMore later. See below.
Add a field in the component state called "loading" and default it to true.
Have a conditional in your render method to load a spinner (or NULL, or something) when "loading" equals true.
In ComponentDidMount*, call your request logic and set state WITHIN the function passed into the .then method after you retrieved all the contents. Make sure to set "loading" to false there.
The conditional in render should then render the contents.
Consider using JSForce (https://jsforce.github.io/) to connect to your object. It already has autoFetch logic included that could replace your queryMore logic.
* Using ComponentDidMount should work but it's probably a bad idea because the component will call out to SF on each render. You should probably use a handler and only call when you don't have the data in state or if you want to refresh for some other reason.

Related

How to cache a lazy load table without external library

So I have a table that I'm currently lazy loading, and I want to cache the data of the 5 most current pages. When a user clicks the forward or backward button, I will send the pageNumber that they are going to, and the direction they are going(prev or next).
In my getData function, I will check if the page they are going to exists in a cachedData array or not. If yes, then I will dispatch that data without making an api call. If no, I will see the direction they are going to. For example, if they are going 1 -> 5, then page 6, I will remove page 1 from the cachedData array, then push page 6 in, and if they are going backward 6 -> 1, I will remove page 6, cuz it's the farthest from current page, and push page 1 instead. Below are the implementations:
const cachedData = [];
async function getData({ payload }) {
const { pageNumber, ...currentPageInfo } = payload;
const cachedPage = cachedData.find(page => page.pageNumber === pageNumber);
if (!cachedPage) {
const response = await fetchData(currentPageInfo);
if (response) {
if (cachedData.length >= 5) {
if (currentPageInfo.direction === "next") cachedData.shift();
cachedData.pop();
}
const { pageInfo, dataList } = response.data;
cachedData.push({
dataList,
pageNumber,
pageInfo,
});
cachedData.sort((x, y) => x.pageNumber - y.pageNumber);
dispatch(getDataSuccess({ res: dataList, pageInfo }));
}
} else {
dispatch(
getDataSuccess({
res: cachedPage.dataList,
pageInfo: cachedPage.pageInfo,
})
);
}
}
Currently it's working, but I'm not sure I'm on the right track. Between the shifting, sorting and finding, the performance aren't very good. The logic is weird because we are using our own way of pagination, and not the usual skip and take approach. But the important thing is whether anything can be improved in this code. Thank you very much.

Unable to add async / await and then unable to export variable. Any help appreciated

Background: Been trying for the last 2 day to resolve this myself by looking at various examples from both this website and others and I'm still not getting it. Whenever I try adding callbacks or async/await I'm getting no where. I know this is where my problem is but I can't resolve it myself.
I'm not from a programming background :( Im sure its a quick fix for the average programmer, I am well below that level.
When I console.log(final) within the 'ready' block it works as it should, when I escape that block the output is 'undefined' if console.log(final) -or- Get req/server info, if I use console.log(ready)
const request = require('request');
const ready =
// I know 'request' is deprecated, but given my struggle with async/await (+ callbacks) in general, when I tried switching to axios I found it more confusing.
request({url: 'https://www.website.com', json: true}, function(err, res, returnedData) {
if (err) {
throw err;
}
var filter = returnedData.result.map(entry => entry.instrument_name);
var str = filter.toString();
var addToStr = str.split(",").map(function(a) { return `"trades.` + a + `.raw", `; }).join("");
var neater = addToStr.substr(0, addToStr.length-2);
var final = "[" + neater + "]";
// * * * Below works here but not outside this block* * *
// console.log(final);
});
// console.log(final);
// returns 'final is not defined'
console.log(ready);
// returns server info of GET req endpoint. This is as it is returning before actually returning the data. Not done as async.
module.exports = ready;
Below is an short example of the JSON that is returned by website.com. The actual call has 200+ 'result' objects.
What Im ultimately trying to achieve is
1) return all values of "instrument_name"
2) perform some manipulations (adding 'trades.' to the beginning of each value and '.raw' to the end of each value.
3) place these manipulations into an array.
["trades.BTC-26JUN20-8000-C.raw","trades.BTC-25SEP20-8000-C.raw"]
4) export/send this array to another file.
5) The array will be used as part of another request used in a websocket connection. The array cannot be hardcoded into this new request as the values of the array change daily.
{
"jsonrpc": "2.0",
"result": [
{
"kind": "option",
"is_active": true,
"instrument_name": "26JUN20-8000-C",
"expiration_timestamp": 1593158400000,
"creation_timestamp": 1575305837000,
"contract_size": 1,
},
{
"kind": "option",
"is_active": true,
"instrument_name": "25SEP20-8000-C",
"expiration_timestamp": 1601020800000,
"creation_timestamp": 1569484801000,
"contract_size": 1,
}
],
"usIn": 1591185090022084,
"usOut": 1591185090025382,
"usDiff": 3298,
"testnet": true
}
Looking your code we find two problems related to final and ready variables. The first one is that you're trying to console.log(final) out of its scope.
The second problem is that request doesn't immediately return the result of your API request. The reason is pretty simple, you're doing an asynchronous operation, and the result will only be returned by your callback. Your ready variable is just the reference to your request object.
I'm not sure about what is the context of your code and why you want to module.exports ready variable, but I suppose you want to export the result. If that's the case, I suggest you to return an async function which returns the response data instead of your request variable. This way you can control how to handle your response outside the module.
You can use the integrated fetch api instead of the deprecated request. I changed your code so that your component exports an asynchronous function called fetchData, which you can import somewhere and execute. It will return the result, updated with your logic:
module.exports = {
fetchData: async function fetchData() {
try {
const returnedData = await fetch({
url: "https://www.website.com/",
json: true
});
var ready = returnedData.result.map(entry => entry.instrument_name);
var str = filter.toString();
var addToStr = str
.split(",")
.map(function(a) {
return `"trades.` + a + `.raw", `;
})
.join("");
var neater = addToStr.substr(0, addToStr.length - 2);
return "[" + neater + "]";
} catch (error) {
console.error(error);
}
}
}
I hope this helps, otherwise please share more of your code. Much depends on where you want to display the fetched data. Also, how you take care of the loading and error states.
EDIT:
I can't get responses from this website, because you need an account as well as credentials for the api. Judging your code and your questions:
1) return all values of "instrument_name"
Your map function works:
var filter = returnedData.result.map(entry => entry.instrument_name);
2)perform some manipulations (adding 'trades.' to the beginning of each value and '.raw' to the end of each value.
3) place these manipulations into an array. ["trades.BTC-26JUN20-8000-C.raw","trades.BTC-25SEP20-8000-C.raw"]
This can be done using this function
const manipulatedData = filter.map(val => `trades.${val}.raw`);
You can now use manipulatedData in your next request. Being able to export this variable, depends on the component you use it in. To be honest, it sounds easier to me not to split this logic into two separate components - regarding the websocket -.

Async issue with State in React Native

I'm trying to build a simple app that lets the user type a name of a movie in a search bar, and get a list of all the movies related to that name (from an external public API).
I have a problem with the actual state updating.
If a user will type "Star", the list will show just movies with "Sta". So if the user would like to see the actual list of "Star" movies, he'd need to type "Star " (with an extra char to update the previous state).
In other words, the search query is one char behind the State.
How should it be written in React Native?
state = {
query: "",
data: []
};
searchUpdate = e => {
let query = this.state.query;
this.setState({ query: e }, () => {
if (query.length > 2) {
this.searchQuery(query.toLowerCase());
}
});
};
searchQuery = async query => {
try {
const get = await fetch(`${API.URL}/?s=${query}&${API.KEY}`);
const get2 = await get.json();
const data = get2.Search; // .Search is to get the actual array from the json
this.setState({ data });
} catch (err) {
console.log(err);
}
};
You don't have to rely on state for the query, just get the value from the event in the change handler
searchUpdate = e => {
if(e.target.value.length > 2) {
this.searchQuery(e.target.value)
}
};
You could keep state updated as well if you need to in order to maintain the value of the input correctly, but you don't need it for the search.
However, to answer what you're problem is, you are getting the value of state.query from the previous state. The first line of your searchUpdate function is getting the value of your query from the current state, which doesn't yet contain the updated value that triggered the searchUpdate function.
I don't prefer to send api call every change of letters. You should send API just when user stop typing and this can achieved by debounce function from lodash
debounce-lodash
this is the best practise and best for user and server instead of sending 10 requests in long phases
the next thing You get the value from previous state you should do API call after changing state as
const changeStateQuery = query => {
this.setState({query}, () => {
//call api call after already changing state
})
}

Firebase functions - Counting children and updating an entry

I have been trying to use Firebase Functions to write a simple method, but I am unfamiliar with JS.
Below is the structure of my Realtime Database
-spots
---is_hidden: false
---likes
------like_id_1: true
---dislikes
------dislike_id_1: true
I am trying to write a simple method that does the following: Whenever an entry is added to dislikes, count the likes and the dislikes.
If the number of dislikes is larger than the number of ( likes + 5 ),
change the value of is_hidden to true
This is my attempt to solving the problem
exports.checkHiddenStatus = functions.database.ref('/spots/{spotid}').onWrite(
(change, context) => {
const collectionRef = change.after.ref;
const isHiddenRef = collectionRef.child('is_hidden');
const likesRef = collectionRef.child('likes');
const dislikesRef = collectionRef.child('dislikes');
if(isHiddenRef.before.val()) return;
let likeCount = likesRef.numChildren();
let dislikeCount = dislikesRef.numChildren();
let isHidden = false;
if( dislikeCount >= (likeCount + 5))
isHidden = true;
if(!isHidden) return;
// Return the promise from countRef.transaction() so our function
// waits for this async event to complete before it exits.
return isHiddenRef.transaction((current) => {
return isHidden;
}).then(() => {
return console.log('Counter updated.');
});
});
Sadly, because I have no experience with JS I keep getting stuck with error messages I don't understand. The most recent being
TypeError: Cannot read property 'val' of undefined
at exports.checkHiddenStatus.functions.database.ref.onWrite (/user_code/index.js:28:28)
Can somebody please help me write this function? Thank you!
It looks like you're trying to treat a database Reference object like a Change object. Change has before and after properties, but a reference does not.
If you have a database reference object, and you want the value of the database at that location, you need to query it with its once() method.
Read more about reading and writing data using the Admin SDK.

Inserting into Collection after Promises in a Meteor Method

I'm using this Gumroad-API npm package in order to fetch data from an external service (Gumroad). Unfortunately, it seems to use a .then() construct which can get a little unwieldy as you will find out below:
This is my meteor method:
Meteor.methods({
fetchGumroadData: () => {
const Gumroad = Meteor.npmRequire('gumroad-api');
let gumroad = new Gumroad({ token: Meteor.settings.gumroadAccessKey });
let before = "2099-12-04";
let after = "2014-12-04";
let page = 1;
let sales = [];
// Recursively defined to continue fetching the next page if it exists
let doThisAfterResponse = (response) => {
sales.push(response.sales);
if (response.next_page_url) {
page = page + 1;
gumroad.listSales(after, before, page).then(doThisAfterResponse);
} else {
let finalArray = R.unnest(sales);
console.log('result array length: ' + finalArray.length);
Meteor.call('insertSales', finalArray);
console.log('FINISHED');
}
}
gumroad.listSales(after, before, page).then(doThisAfterResponse); // run
}
});
Since the NPM package exposes the Gumorad API using something like this:
gumroad.listSales(after, before, page).then(callback)
I decided to do it recursively in order to grab all pages of data.
Let me try to re-cap what is happening here:
The journey starts on the last line of the code shown above.
The initial page is fetched, and doThisAfterResponse() is run for the first time.
We first dump the returned data into our sales array, and then we check if the response has given us a link to the next page (as an indication as to whether or not we're on the final page).
If so, we increment our page count and we make the API call again with the same function to handle the response again.
If not, this means we're at our final page. Now it's time to format the data using R.unnest and finally insert the finalArray of data into our database.
But a funny thing happens here. The entire execution halts at the Meteor.call() and I don't even get an error output to the server logs.
I even tried switching out the Meteor.call() for a simple: Sales.insert({text: 'testing'}) but the exact same behaviour is observed.
What I really need to do is to fetch the information and then store it into the database on the server. How can I make that happen?
EDIT: Please also see this other (much more simplified) SO question I made:
Calling a Meteor Method inside a Promise Callback [Halting w/o Error]
I ended up ditching the NPM package and writing my own API call. I could never figure out how to make my call inside the .then(). Here's the code:
fetchGumroadData: () => {
let sales = [];
const fetchData = (page = 1) => {
let options = {
data: {
access_token: Meteor.settings.gumroadAccessKey,
before: '2099-12-04',
after: '2014-12-04',
page: page,
}
};
HTTP.call('GET', 'https://api.gumroad.com/v2/sales', options, (err,res) => {
if (err) { // API call failed
console.log(err);
throw err;
} else { // API call successful
sales.push(...res.data.sales);
res.data.next_page_url ? fetchData(page + 1) : Meteor.call('addSalesFromAPI', sales);
}
});
};
fetchData(); // run the function to fetch data recursively
}

Categories