Unable to implement pagination in DynamoDB DocumentClient - javascript

I'm using a React/DynamoDB stack for a project. I'm trying to get pagination to work using the scan method in DynamoDB DocumentClient. Here's the function I'm using:
async function getItems(lastItem?) {
try {
const params = {
TableName: "posts",
Limit: 1,
};
if (lastItem) {
params.ExclusiveStartKey = { item_id: lastItem };
}
const response = await docClient.scan(params).promise();
console.log(response)
return {
items: response.Items,
lastItem: response.LastEvaluatedKey,
};
} catch (error) {
throw error;
}
}
I use this component to console.log this response:
import { getItems } from "../../atoms/functions/AWS";
function Test() {
return (
<div>
<button onClick={() => getItems()}>get item</button>
</div>
);
}
However, with each click I'm getting the same result:
Object { Items: (1) […], Count: 1, ScannedCount: 1, LastEvaluatedKey: {…}, … }
"$response": Object { retryCount: 0, redirectCount: 0, maxRetries: 10, … }
​Count: 1
​Items: Array [ {…} ]
​LastEvaluatedKey: Object { postId: "2" }
ScannedCount: 1
I'm using a mock DynamoDB table with 5 posts - postId: "1", postId: "2", and so on.
Am I implementing the getItems function incorrectly? How can I modify the above code to paginate through all the posts?
EDIT:
I updated my component in line with Mark B's suggestion:
function Test() {
const [item, setItem] = useState();
useEffect(() => {
getItems().then((resp) => setItem(resp));
}, []);
return (
<div>
<button onClick={() => getItems(item)}>get item</button>
</div>
);
}
And replaced return {items: response.Items, lastItem: response.LastEvaluatedKey}; with return response.LastEvaluatedKey;.
I thought that this would finally work. However, I'm getting the following error:
Uncaught (in promise) ValidationException: The provided starting key is invalid: The provided key element does not match the schema

Related

access global script function vue

How
<script>
const fakeApiRequest = (id) => {
return id;
};
export default {
data() {
return {
ids: [1, 2, 3, 4, 5, 6],
};
},
methods: {
newValues(id) {
this.ids.forEach((el) => el.fakeApiRequest(id));
}
},
};
</script>
How can I access global script function - const fakeApiRequest ? Meanwhile using Window object (alike window.fakeApiRequest ) is useless also.
You can try something like this:
<script>
export default {
data() {
return {
ids: [1, 2, 3, 4, 5, 6],
};
},
methods: {
fakeApiRequest(id){
return id
},
newValues(id) {
this.ids.forEach((element) => {
console.log(this.fakeApiRequest(element))
});
}
},
};
</script>
<template>
<div>
{{newValues(5)}}
</div>
</template>
Welcome to stackoverflow,
the first thing that came to my mind was if it is really needed to do that many api requests. Just a side note: wouldn’t it be better to receive the data for multiple ids at the same time with only one API call?
I think in your example, that you posted into the codesandbox the problem is, that you don’t set the new array. The call to [].forEach is not setting anything. As you call an api, you need to make sure, all requests are resolved.
As some of the requests might be rejected, you could await Promise.allSettled and filter the results by the status:
const fakeApiRequest = async (id) => {
// await some api call...
return id;
};
export default {
methods: {
async newSuccessIds() {
const results = await Promise.allSettled(
// the fakeApiRequest can be called directly
// as it is part of the closure context.
this.ids.map(fakeApiRequest)
);
return results
.filter(({ status }) => status === 'fulfilled')
.map(({ value }) => value)
}
},
};

Apollo resolver chain returning undefined? Cannot read properties of undefined

I'm new to Apollo (back-end in general) so your patience is appreciated; I've looked through Apollo's docs and I'm not sure where I've gone wrong.
The data I'm receiving from my REST API call is as follows - I'm trying to return the ids and titles and based on my trials, I'm fairly certain the issue is my resolvers? The error I'm receiving is: "Cannot read properties of undefined (reading: 'session')"
{
"selection": {
...other data...
"options": [
{
id: 1
title: "Option 1 Title"
},
{
id: 2
title: "Option 2 Title"
},
]
}
}
My schema:
type Query {
session: Selection
}
type: Selection {
...other data...
optionListing: [Options]
}
type: Options {
id: Int
title: String
}
My resolvers:
{
Query: {
session: async (parent, args, {tokenAuth}) => {
...token auth code....
return tokenAuth;
};
}
Selection: {
optionListing: async ({tokenAuth}) => {
...this is the resolver that triggers the API call...
return optionData;
}
}
Options: {
id: async(parent) => {
const tempID = await parent;
return tempID.id;
}
title: async(parent) => {
const tempTitle = await parent;
return tempTitle.title;
}
}
}

Recursive function only returning first result

I'm trying to implement a file upload feature using native DnD and the browser API.
I have to functions like so:
export function buildFileTree(allObjects) {
let fileTree = {}
allObjects.forEach((item, index) => {
traverseFileTree(fileTree, null, item)
})
return fileTree
}
export function traverseFileTree(fileTree, parent = null, item) {
let parentId = !!parent ? fileTree[parent].id : parent
if (item.isFile) {
item.file(file => {
fileTree[item.fullPath] = {
id: uuidv4(),
name: item.name,
parent_id: parentId,
file: item
}
})
}
if (item.isDirectory) {
fileTree[item.fullPath] = {
id: uuidv4(),
name: item.name,
is_directory: true,
parent_id: parentId,
file: null
}
let dirReader = item.createReader()
dirReader.readEntries(entries => {
entries.forEach((entry, index) => {
traverseFileTree(fileTree, item.fullPath, entry)
})
})
}
}
Which I use like so, and I'm getting very weird results, when I console.log the object, it shows the correct data, but when I try to stringify it, access any other attributes, or iterate over it; it's only showing the first result.
const handleUploadDrop = (files) => {
const finalFiles = buildFileTree(files)
console.log(finalFiles) // This shows the correct data
console.log(JSON.stringify(finalFiles)) // This only shows 1 item!!!
console.log(Object.keys(finalFiles).length) // This prints 1
}
I'm very confused by this and any help would be greatly appreciated.

I'm trying to map over objects, nested in objects that are nested in an array. What's the best way of tackling this?

I am receiving data from an API containing cryptocurrency market data. When a user searches for crypto the information of that crypto is fetched and added to the state. I would like to have each item that is returned from the API render to the user, slowly making a list of the data and sub-data.
I'm aware of mapping on an array, but cannot seem to get objects mapped (having attempted 'Object.key.map' too)
I have tried to target the items in the object, rather then the full objects returned themselves, but React doesn't seem to like me using uppercase in my code, with the dot notation of getting items from an object (uppercase appears to be required by the API as it returns data with uppercase:
(3) [{…}, {…}, {…}]
0:
XTZ:
USD: 0.9505
__proto__: Object
__proto__: Object
1:
BTC:
USD: 9823.95
__proto__: Object
__proto__: Object
2:
ETH:
USD: 216.81
__proto__: Object
__proto__: Object
length: 3
__proto__: Array(0)
here are results from 3 returned query to the API.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
cryptos: [],
term: '',
error: undefined
};
}
getstuff = (symbol) => {
axios.get(`https://min-api.cryptocompare.com/data/pricemulti?fsyms=${symbol}&tsyms=USD`)
.then(res => {
const crypto = res.data;
console.log(crypto);
if (res.data.Response === 'Error') {
console.log('found error');
const error = res.data.Message;
if (error) {
if (error.includes('toSymbol/s')){
this.setState({error: `We cannot find ${symbol} in our list of cryptos. Please ensure you're using the symbol, not the full name (ie 'BTC', not 'Bitcoin')`})
console.log(this.state);
} else {
this.setState({error: `Hmmmm, we're run into an error, and frankly have no idea what it is.`})
}
}
} else {
this.setState({ cryptos: [...this.state.cryptos, crypto] });
}
})
}
onInputChange = (e) => {
console.log(e.target.value)
this.setState({ term: e.target.value});
}
onFormSubmit = (e) => {
e.preventDefault();
this.getstuff(this.state.term.toUpperCase());
this.setState({term: ''})
}
render() {
return (
<div>
<form onSubmit={this.onFormSubmit}>
<div className="field">
<label>Video Search</label>
<input
type="text"
value={this.state.term}
onChange={this.onInputChange}/>
</div>
</form>
{`you searched ${this.state.term}`}
{this.state.cryptos.map((item,i) => <li key={i}>item</li>)}
{this.state.error && <p>{this.state.error}</p>}
<button onClick={() => {console.log(this.state.cryptos)}}> console log current state </button>
</div>
)
}
}
I'm trying to extract and render the cryptocurrency name that is returned (should match user query), as well as the price (in USD) that is returned with it.
ideally along the lines of
Selection 1: BTC, $9452 at the time of the query.
Selection 2: ETH, $202 at the time of the query.
Selection 3: XTZ, $0.92 at the time of the query.
If i have an object like this:
obj = [
{ name: "Jon", ID: { num: 23, country: "Canada" } },
{ name: "Mary", ID: { num: 28, country: "Russia" } },
{ name: "Jon2", ID: { num: 12, country: "Germany" } }
];
I would iterate like so:
obj.map(element => { Object.keys(element).map( key => console.log(element[key]))})
request ?fsyms=EUR,GBP&tsyms=USD
gives me:
{
"EUR":{ "USD":1.124 },
"GBP":{ "USD":0.03747 }
}
I would iterate it like this:
const data = {
"EUR":{ "USD":1.124 },
"UAH":{ "USD":0.03747 }
};
Object.keys(data).forEach((key, i) => {
console.log(`Selection ${i}: ${key}, $${data[key]["USD"]} at time of query.`);
});
If you add more matches to the internal object, then you can iterate like this:
const data = {
"EUR":{ "USD":1.124, "GBP":0.8991 },
"UAH":{ "USD":0.03747, "GBP":0.03016 }
};
let i = 0;
Object.keys(data).forEach(key => {
const values = data[key];
Object.keys(values).forEach(key2 => {
console.log(`Selection ${++i}: ${key}, ${key2} is $${data[key][key2]}.`);
});
});
Which will result in:
Selection 1: EUR, USD is $1.124.
Selection 2: EUR, GBP is $0.8991.
Selection 3: UAH, USD is $0.03747.
Selection 4: UAH, GBP is $0.03016.

ReactiveAggregate() + collection.update() -> Error: Expected to find a document to change

The reactive aggregation is published to the client initially without errors. The error seems to be triggered, when the Meteor.user collection is being updated by a Meteor.call() from the client:
updateProductFavorites = (product_key, action) => {
const { ranking } = this.props
const { product_keys } = ranking[0]
Meteor.call('Accounts.updateProductFavorites', product_key, action, (err, response) => {
if (err)
makeAlert(err.reason, 'danger', 3000)
else
this.getProductsByKeys(product_keys)
})
}
I have subscribed to both the Meteor.user() and the reactive aggregation:
export default withTracker(() => {
const handle = Meteor.subscribe("products.RankingList")
return {
ranking: AggregatedProductRanking.find({}).fetch(),
user: Meteor.user(),
isLoading: !handle.ready() || !Meteor.user()
}
})(ProductRankingList)
I have declared and imported the clientCollection on both sides, as also suggested in this answer. This is the relevant code on the server side:
const getProductRankingList = (context) => ReactiveAggregate(context, Meteor.users, [
// aggregation stages can be seen in the code snippet below
], { clientCollection: "aggregatedProductRanking"})
Meteor.methods({
'Accounts.updateProductFavorites': function(product_key, action) {
allowOrDeny(this.userId)
action = action == 'add' ? { $addToSet: { productFavorites: product_key }} : { $pull: { productFavorites: product_key }}
return Meteor.users.update({_id: this.userId}, action)
}
})
Meteor.publish('products.RankingList', function() {
const callback = () => this.stop()
allowOrDenySubscription(this.userId, callback)
return getProductRankingList(this)
})
What baffles me, is that the update called by Meteor.call('Accounts.updateProductFavorites') is still executed reliably, even with this error being thrown.
So the change to the logged in Meteor.user() flows back to the client and the component rerenders. Only the subscription of the ReactiveAggregate seems to stop working. The following error is thrown and I have to reload the browser to see the changes in the aggregation result. (full stack trace at the bottom)
Uncaught Error: Expected to find a document to change
at Object.update (collection.js:207)
at Object.store.<computed> [as update] (livedata_connection.js:310)
...
// In a certain case the error message is a bit different:
Exception in flushing DDP buffered writes: Error: Expected to find a document to change
at Object.update (collection.js:207)
at Object.store.<computed> [as update] (livedata_connection.js:310)
...
I am guessing that this update() is called by the ReactiveAggregate() in order to populate the clientCollection. But what am I doing wrong?
For a more complete code sample:
Server side
import { Meteor } from 'meteor/meteor'
import { ReactiveAggregate } from 'meteor/jcbernack:reactive-aggregate';
// Collections
import { AggregatedProductRanking } from '../imports/collections'
const getProductRankingList = (context) => ReactiveAggregate(context, Meteor.users, [
{
$match: { productFavorites: {$ne: [] }}
},{
$project: {
_id: 0,
productFavorites: { $concatArrays: "$productFavorites" },
}
},{
$unwind: "$productFavorites"
},{
$facet: {
rankingList: [
{
$group: {
_id: "$productFavorites",
count: { $sum: 1 }
}
},{
$sort: { "count": -1 }
}
],
product_keys: [
{
$group: {
_id: 0,
product_keys: { $addToSet: "$productFavorites" }
}
}
]
}
},{
$unwind: "$product_keys"
},{
$project: {
_id: 0,
rankingList: 1,
product_keys: "$product_keys.product_keys"
}
}
], { clientCollection: "aggregatedProductRanking"})
Meteor.methods({
'Accounts.updateProductFavorites': function(product_key, action) {
allowOrDeny(this.userId)
action = action == 'add' ? { $addToSet: { productFavorites: product_key }} : { $pull: { productFavorites: product_key }}
return Meteor.users.update({_id: this.userId}, action)
},
'Products.getByProductKey': function(productFavorites) {
allowOrDeny(this.userId)
if (productFavorites == undefined)
productFavorites = Meteor.users.findOne({_id: this.userId}, {fields: {productFavorites: 1}}).productFavorites
if (productFavorites.length > 0) {
return Products.find(
{ product_key: {$in: productFavorites }, price_100_g_ml: {$ne: null} },
{ sort: {product_name: 1} }).fetch()
} else
return []
},
})
function allowOrDenySubscription(userId, callback) {
if (!userId) {
callback()
return;
}
}
Meteor.publish(null, function() {
if (!this.userId)
return false
return Meteor.users.find({_id: this.userId}, { fields: {
firstName: 1, lastName: 1,
zip: 1, city: 1, street: 1, houseNumber: 1,
phone: 1, iban: 1, bic: 1,
memberId: 1, membershipFee: 1,
productFavorites: 1
}})
}, { is_auto: true })
Meteor.publish('products.RankingList', function() {
const callback = () => this.stop()
allowOrDenySubscription(this.userId, callback)
return getProductRankingList(this)
})
Client side
import { Meteor } from 'meteor/meteor';
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
// ... more imports
// Collections
import { AggregatedProductRanking } from '../../../imports/collections';
class ProductRankingList extends Component {
constructor() {
super()
this.state = {
products: [],
productDetails: true,
singleProductDetails: 0,
}
}
getProductsByKeys = (product_keys) => {
Meteor.call('Products.getByProductKey', product_keys, (err, response) => {
if (err)
makeAlert(err.reason, 'danger', 3000)
else {
this.setState({products: response})
console.log(response)
}
})
}
updateProductFavorites = (product_key, action) => {
const { ranking } = this.props
const { product_keys } = ranking[0]
console.log(product_keys)
Meteor.call('Accounts.updateProductFavorites', product_key, action, (err, response) => {
if (err)
makeAlert(err.reason, 'danger', 3000)
else
this.getProductsByKeys(product_keys)
})
}
toggleProductFavorite = (product_key) => {
const { productFavorites } = this.props.user
if (productFavorites.includes(product_key))
this.updateProductFavorites(product_key, 'remove')
else
this.updateProductFavorites(product_key, 'add')
}
mapProductFavorites = () => {
const { products, productDetails, singleProductDetails } = this.state
const { productFavorites } = this.props.user
const { ranking } = this.props
const { rankingList } = ranking[0]
if (products.length == 0)
return <div className="alert alert-primary col-12">No one has favorited any products at the moment, it seems.</div>
products.map((product, i) => {
const { order_number, supplierId } = product
product["count"] = rankingList.find(product => product._id == `${supplierId}_${order_number}`).count
})
products.sort((a, b) => b.count - a.count)
return (
products.map((product, i) => {
if (product.price_100_g_ml) {
var [euro, cent] = product.price_100_g_ml.toFixed(2).toString().split('.')
}
const { product_name, units, trading_unit, certificate, origin, order_number, supplierId, count } = product
const isFavorite = productFavorites.includes(`${supplierId}_${order_number}`) ? 'is-favorite' : 'no-favorite'
return (
<div className="col-lg-6" key={i}>
<div key={i} className="product-card">
<div className="card-header" onClick={() => this.toggleSingleProductDetails(order_number)}>
{product_name}
{/* <span className="fa-layers fa-fw heart-with-count">
<FontAwesomeIcon icon="heart"/>
<div className="fa-layers-text">{count}</div>
</span> */}
</div>
{productDetails || singleProductDetails == order_number ?
<>
<div className="card-body">
{euro ?
<>
<div className="product-actions">
<button className={`btn btn-light btn-lg product-${isFavorite}`}
onClick={() => this.toggleProductFavorite(`${supplierId}_${order_number}`)}>
<FontAwesomeIcon icon="heart"/>
<span className="ml-2">{count}</span>
</button>
</div>
<div className="price-100-g-ml">
<small>pro 100{units == 'kg' ? 'g' : 'ml'}</small><sup></sup>
<big>{euro}</big>.<sup>{cent.substring(0,2)}</sup>
</div>
</> : null}
</div>
<div className="card-footer">
<div className="row">
<div className="col-4">{trading_unit}</div>
<div className="col-4 text-center">{certificate}</div>
<div className="col-4 text-right">{origin}</div>
</div>
</div>
</> : null }
</div>
</div>)
})
)
}
componentDidUpdate(prevProps, prevState) {
const { isLoading, ranking } = this.props
if (ranking.length < 1)
return null
if (!isLoading && (prevProps.ranking != ranking)) {
this.getProductsByKeys(ranking[0].product_keys)
}
}
render() {
const { isLoading, ranking } = this.props
if (isLoading || ranking.length < 1)
return null
return(
<div className="row mt-3">
{this.mapProductFavorites()}
</div>
)
}
}
export default withTracker(() => {
const handle = Meteor.subscribe("products.RankingList")
return {
ranking: AggregatedProductRanking.find({}).fetch(),
user: Meteor.user(),
isLoading: !handle.ready() || !Meteor.user()
}
})(ProductRankingList)
Full Stack Trace
// When loading component through history.push()
Exception in flushing DDP buffered writes: Error: Expected to find a document to change
at Object.update (collection.js:207)
at Object.store.<computed> [as update] (livedata_connection.js:310)
at livedata_connection.js:1192
at Array.forEach (<anonymous>)
at livedata_connection.js:1191
at Array.forEach (<anonymous>)
at Connection._performWrites (livedata_connection.js:1187)
at Connection._flushBufferedWrites (livedata_connection.js:1167)
at meteor.js?hash=857dafb4b9dff17e29ed8498a22ea5b1a3d6b41d:1234
// After second call of Meteor.call('Accounts.updateProductFavorites')
Uncaught Error: Expected to find a document to change
at Object.update (collection.js:207)
at Object.store.<computed> [as update] (livedata_connection.js:310)
at livedata_connection.js:1192
at Array.forEach (<anonymous>)
at livedata_connection.js:1191
at Array.forEach (<anonymous>)
at Connection._performWrites (livedata_connection.js:1187)
at Connection._flushBufferedWrites (livedata_connection.js:1167)
at Connection._livedata_data (livedata_connection.js:1133)
at Connection.onMessage (livedata_connection.js:1663)
It turned out, that the solution was pretty simple: The reason for the above mentioned errors was a missing _id field in the aggregation result:
...
},{
$project: {
_id: 0, // This will give the error.
rankingList: 1,
product_keys: "$product_keys.product_keys"
}
}
], { clientCollection: "aggregatedProductRanking"})
The docs for ReactiveAggregate() (meteor-reactive-aggregate) state that the _id field can be omitted, as it will be created automatically by ReactiveAggregate(). But even after removing the _id: 0, it didn't work.
What does work is this:
...
},{
$project: {
_id: "whatTheFuckIsGoingOnHere", // Calm down, bro!
rankingList: 1,
product_keys: "$product_keys.product_keys"
}
}
], { clientCollection: "aggregatedProductRanking"})
Wonderfully reactive aggregations, just for a little pain in the a**.
I made a bug report in the github repo

Categories

Resources