Svelte break in each - database

So i have a database that i recieve an array of objects(JSON) that i iterate over using each but if there are like a 1000 objects to iterate over then i don't want to make all of them appear at one i want a "page" mechanism where each page contains around 30 and then the next page contains the next 30 and so on and so on.
But in order to do this i would need to "break" in the each block.
If anyone has a better idea on how to do this then please share your idea/source since i don't think the implementation i came up with is very good.

You could slice your big array to just iterate over the relevant objects in the each block.
Example (REPL)
<script>
const database = Array.from({ length: 1000 }, (_, index) => ({
id: index,
text: Math.random().toString()
}))
const pageSize = 30;
let offset = 0;
</script>
{#each database.slice(offset, offset + pageSize) as el (el.id)}
<div>{el.text}</div>
{/each}
<button
disabled={offset === 0}
on:click={() => offset -= pageSize}
>
Previous
</button>
<button
disabled={offset + pageSize >= database.length}
on:click={() => offset += pageSize}
>
Next
</button>

Related

#each statement on HTML showing only last item

I need to print in my list all the items of my array, but I'm getting only the last item printed. When I console.log it, I can see all the items. Not sure why this is happening, I tried to figure out using all I've got, but none worked.
let examples = [];
onMount(async () => {
const response = await fetch(
"https://<my-api>/db/v1/cosmos/{container}",
{
method: "POST",
headers: {
"<api-key>":
"<my-api-key>",
},
body: JSON.stringify({
query: "SELECT * FROM c ORDER BY c.name ASC",
}),
}
).then((response) => response.json());
for (let i = 0; i < response.data.length; i++) {
console.log(response.data[i].name);
examples = response.data[i].name;
}
});
On my HTML I'm using the following loop:
{#each examples as example}
<ListgroupItem class="text-base font-semibold gap-2">
Name: {examples} <br />
</ListgroupItem>
{/each}
What I'm getting in return, is just only the last item of my array. If I use example instead of examples, on my foreach loop, my return is only the letters.
When I'm using SvelteKit and Cosmos DB.
Something I don't get is that if the item has 5 letters, it will print 5 rows. If another item has 7 letters, it will print 7 rows. Not sure why. I am not getting any error.
Any ideas what I'm doing wrong?
I also have tried using those below, but they didn't work.
examples.unshift(0);
examples.push(examples);
You are always assigning to what is supposed to be a list, the name of the current item, so at the end you are left with just the name of the last item:
for (let i = 0; i < response.data.length; i++) {
console.log(response.data[i].name);
examples = response.data[i].name;
}
If you only want a list of name this should be replaced with:
examples = response.data.map(x => x.name)
The #each is also wrong, it iterates the variable but you are not using the item, but the list variable, it should be:
{#each examples as example}
<ListgroupItem class="text-base font-semibold gap-2">
Name: {example} <br /> <!-- example, not examples -->
</ListgroupItem>
{/each}
(As examples was just a string before, it iterated the letters in the string.)

Each child in a list should have a unique "key" prop error despite method to create unique key

In my application I am currently getting the react warning:
Warning: Each child in a list should have a unique "key" prop.
Check the render method of GetReplies.
This is the GetReplies method:
export function GetReplies(props) {
const Id = props.Id;
const replies = allComments.filter(obj => obj.ParentCommentId === Id).
sort((objA, objB) => new Date(objB.Time) - new Date(objA.Time));
console.log(generateKey(Id));
if (Object.keys(replies).length !== 0) {
return (
<div key = {"replies-container-" + generateKey(Id)} id={"replies-container-" + Id} className="replies-container">
<div key ={"panel-heading replies-title" + generateKey(Id)} className="panel-heading replies-title">
<a key = {"accordion-toggle replies-" + generateKey(Id)} className="accordion-toggle replies-a collapsed" data-parent={"#replies-container-" + Id} data-toggle="collapse" data-target={"#replies-for-" + Id}>Replies</a>
</div>
<div key = {"replies-for-" + Id} id={"replies-for-" + generateKey(Id)} className="replies-list collapse">
{
<React.Fragment>
{ Object.entries(replies).reverse().map(([key, arr]) => {
return (
<GetComments commentsArray = {replies}/>
)
}) }
</React.Fragment>
}
</div>
</div>
);
}
}
and this is the GetComments Method it calls:
export function GetComments({ commentsArray }) {
return (
<React.Fragment>
{commentsArray.map((comment) => {
const localId = comment.LocalId;
const parentCommentId = comment.ParentCommentId;
const parentLocalId = allComments.filter(obj => obj.Id === parentCommentId);
const recipients = comment.Recipients;
let recipientsArray = [];
let recipientsList;
recipients.forEach(function (arrayItem) {
recipientsArray.push(arrayItem.Name);
recipientsList = recipientsArray.join(', ');
});
console.log(generateKey(localId));
const date = new Date(comment.Time);
const formattedDate = date.toLocaleDateString() + " " + ("0" + date.getHours()).slice(-2) + ":" + ("0" + date.getMinutes()).slice(-2);
return (
<div key={generateKey(localId)} className="comment-container">
<div key={generateKey(comment.Commenter.ItemId)} className="commenter">
<span className="id-label">{localId}</span>
{parentCommentId && (
<span className="reply" title={`in reply to ${parentLocalId[0].LocalId}`}>
<a className="reply" href={"#c" + parentLocalId[0].LocalId}>⤵</a> </span>
)}
<span><a id={"c" + localId} name={"c" + localId}>{comment.Commenter.Name}</a></span>
<div key={generateKey(localId) + "-comment-actions-container "} className="comment-actions-container">
<button type="button" className="btn-reply" data-value={comment.Id} title="Reply to comment" data-toggle="modal" data-target="#dlg-new-comment">⥅</button>
</div>
</div>
<div key={generateKey(localId) + "-recipients "} className="recipients">{recipientsList}</div>
<div key={generateKey(localId) + "-comment "} className="comment">{comment.Comment}</div>
<div key={generateKey(localId) + "-comment-footer "} className="comment-footer">{formattedDate}</div>
<GetReplies Id = {comment.Id}/>
</div>
);
})}
</React.Fragment>
);
}
To help make sure each key is unique I made this generate key method:
const generateKey = (pre) => {
return `${ pre }_${ new Date().getTime() }`;
}
However I am still getting that unique key error and I have no idea what it is I could be missing?
I am even tried to expand upon it by doing:
const generateKey = (pre) => {
let index =0;
return `${ pre }_${ new Date().getTime()}_${index++}`;
}
but index would always equal 0? So I'm not sure why that wasn't incrementing either, any advice would be appreciated
Even though your generateKey might work (not sure though since it will get called quite quickly when mapping over an array) you are adding the key to the wrong part of your code.
React expects a key on every item in a for-loop. In your case you have added a key to every JSX element, except for those within the Object.entries(replies).reverse().map
You could probably fix your problem by adding keys in there, like so:
{Object.entries(replies).reverse().map(([key, arr]) => {
return (
<GetComments key={key} commentsArray = {replies}/>
)
})}
To add a note though, React uses the key value to recognize changes on re-renders. In case your array gets re-ordered it can use the keys to prevent enormous re-renders. The best practice here would be to add a key that is related to the data itself, not the location in an array or a random number.
In addition of the previous answer :
const generateKey = (pre) => {
let index =0;
return `${ pre }_${ new Date().getTime()}_${index++}`;
}
will always have a 0 index, because each function call will create his own scope, with his own "index" variable. To increment the index on each call, you must extract the "index" variable outside of the function, so each function call will share the same index variable, from the 'outside' scope (but using the item's index remains the best idea, if you have no unique key you can use from your data).
Using "new Date().getTime()" to generate keys is also a bad idea, because your code will be ran so quick that a few components could (and will) share the same timestamp.
An alternative is to use a third party library, like 'uuid', to generate unique ids, but it must be used carefully.
You should use the index of the commentsArray.map
like :
{commentsArray.map((comment, index) => {
...
key={generateKey(Id, index)}
and then :
const generateKey = (pre, index) => `${pre}_${index}`;
You are filtering with Id values, so your key will be the same for each childs, that's why you need the commentsArray index.

Need to loop the Icon and display Star Icon 5 times using React [duplicate]

I am using React/JSX and Lodash in my app in order to accomplish what I want.
I need to repeat an element a certain amount of times depending on a condition. How should I do that?
Here is the element:
<span className="busterCards">♦</span>;
And I am assigning it like this:
let card;
if (data.hand === '8 or more cards') {
card = <span className='busterCards'>♦</span>;
}
So in this case, I need to repeat the element 8 times. What should be the process using Lodash?
The shortest way to do this without any external libraries:
const n = 8; // Or something else
[...Array(n)].map((e, i) => <span className="busterCards" key={i}>♦</span>)
solution without lodash or ES6 spread syntax:
Array.apply(null, { length: 10 }).map((e, i) => (
<span className="busterCards" key={i}>
♦
</span>
));
Here you go:
let card = [];
_.times(8, () => {
card.push(<span className="busterCards">♦</span>);
});
You may want to add key to each span element so React won't complain about missing the key attribute:
let card = [];
_.times(8, (i) => {
card.push(<span className="busterCards" key={i}>♦</span>);
});
For more info about .times, refer here: https://lodash.com/docs#times
Implementing this without Lodash
<section>
{Array.from({ length: 10 }, (_, i) => <span key={i}>Your text</span>)}
</section>
How does this work?
Array.from() is used in two contexts:
Creating an array from an array-like data structure. For example, we can convert a map into an array using Array.from()
const map = new Map([ [1, 2], [3, 4], [4, 5] ])
console.log(Array.from(map)) //gives an array - [[1, 2], [3, 4], [4, 5]]
Creating an array and filling out the values (This can be handy when we need to create an array containing more elements)
Array.from() accepts an object and a callback function.
Array.from({ length: 7 }, (() => 10)) // gives [10,10,10,10,10,10,10]
We can take advantage of the index (second parameter) inside the callback function to provide unique array elements
Array.from({ length: 4 }, ((_, i) => i + 1)) // [1,2,3,4]
I'm using this and works for me.
[...Array(10)].map((elementInArray, index) => (
<div key={index}>
Text in Loop
</div>
))
Using _.times: https://jsfiddle.net/v1baqwxv/
var Cards = React.createClass({
render() {
return <div>cards {
_.times( this.props.count, () => <span>♦</span> )
}</div>;
}
});
You could do it like this (without lodash):
var numberOfCards = 8; // or more.
if (data.hand >= numberOfCards) {
var cards = [];
for (var i = 0; i < numberOfCards; i++) {
cards[i] = (<span className="busterCards">♦</span>);
}
}
Straight forward options ways to do that without any external libraries (2021):
// straight forward but without key index. Not so good for react but work fine with worning
Array(X).fill(<span className="busterCards">♦</span>)
// with index
Array(X).fill().map((v,i)=> <span className="busterCards">♦</span>)
Array.from( Array(X), (v,i) => <span key={i} className="busterCards">♦</span> )
// same thing basically
Array.from( {length:X}, (v,i) => <span key={i} className="busterCards">♦</span> )
[...Array(3)].map( (_,i)=> <span key={i} className="busterCards">♦</span> )
You can create an array with as many items as you need rendered and then map through the array to render the correct number of elements you need.
const totalItems = 8;
const items = new Array(totalItems).fill(null);
// .... then
return (
{items.map((_, idx) => <span className="busterCards" key = {idx}>♦</span>)}
);
You can use Lodash range.
_.range(20).map((_n, i) => <MyComponent key={i}/>)
I thought, that if someone would like to use it in many places in the code, it would be a good idea to make it an npm package: https://www.npmjs.com/package/repeat-component. I think it will help someone :)

Is there a react method to dynamically add items from an array to a div in a custom pattern

I am using a div that contains an image below which rows are added with products in them. The product gallery must look like a pyramid so below the image there will be three product rows each containing one, two and three products (each product being a custom rfce), respectively.
Illustration:
img
div1 => product
div2 => product product
div3 => product product product
I used a function that renders a string with all the desired attributes and tried using parse (from html-react-parse) to convert the string to html tags but this method seems slightly unprofessional in addition to which upon attempting to parse the string (which was formatted correctly), I am getting an error.
How can I rectify this, and is there a better method I can use?
string generation method:
const getProducts = () => {
var cur = 0
var gallery = ``
for(var i = 1; i <= 3; i++) {
var prods = ``
for(var j = 0; j < i; j++) {
const product = products[cur++]
// <Product /> is a custom rfce.
prods += `<Product
id = ${product.id}
pname = ${product.pname}
price = ${product.price}
rating = ${product.rating}
pic = ${product.pic}
altPic = ${product.altPic}
/>`
}
gallery += `<div className = 'product_row'>
${prods}
</div>`
}
return ({gallery})
}
method call:
return (
...
<div className = 'product_container'>
<img src = {banner} alt = 'banner' />
{parse(getProducts())}
</div>
...
)
the string is displayed just fine in the body before using parse but upon using it I get the following error after the line const product = products[cur++] in getProducts():
TypeError: product is undefined
Even if I get parse to work, I'd like to use another method as I'm sure there is a cleaner alternative.
Since you know the shape (JSX) you want, I would just hardcode the array chunks. Create a function to render an array into a row, and use the utility for each row, slicing the products state into sub-arrays to be rendered into the rows.
Array.prototype.slice
The slice() method returns a shallow copy of a portion of an array
into a new array object selected from start to end (end not included)
where start and end represent the index of items in that array. The
original array will not be modified.
const renderRow = row => (
<div className='product_row'>
{row.map(product => (
<Product
key={product.id}
id={product.id}
pname={product.pname}
price={product.price}
rating={product.rating}
pic={product.pic}
altPic={product.altPic}
/>
))}
</div>
);
return (
...
<div className = 'product_container'>
<img src={banner} alt='banner' />
{renderRow(products.slice(0,1))}
{renderRow(products.slice(1,3))}
{renderRow(products.slice(3,6))}
</div>
...
);

Angular - Firebase - Repeating DIV ng-repeat duplicating new rows

In running into a problem where I have a repeating DIV on my page, but I am getting duplicates when I add new rows. So for example if I have Row A and and Row B, and then I click "Add" to add a new row, I would like to see 3 rows. However I get 5 rows, Row A, Row B, then Row A, Row B and the new Row C.
The page initializes fine, will display only the 3 rows, but what am I doing wrong with the "Add" of new rows...It appears to be not refreshing as I would like?
Any help would be great!
Thanks!
My ng-repeat looks like this:
<div ng-repeat="infant in Infants" class="list card">
<h2>{{infant.name}} - {{infant.ID}}</h2>
<a class="item item-icon-left assertive" href="#"> <i class="icon ion-ios-analytics"></i> Last measurement 188mm / 190mm (size 5.5) </a>
</div>
The initialisation of Infants above is achieved with a global array:
$scope.Infants = [];
...
if (firebaseUser) {
//set the user and infant list variables
$log.log("Signed in as:", firebaseUser.uid);
var usersRef = firebase.database().ref('users');
loggedInUser = usersRef.child(firebaseUser.uid);
loggedInUser.on('value', snapshot => {
$log.log("UserDetails:", snapshot.val());
});
InfantList = loggedInUser.child('infantList');
InfantList.on('value', snapshot => {
$log.log("InfantDetails:", snapshot.val());
angular.forEach(snapshot.val(), function (value, key) {
$log.log("val", value);
$log.log("key", key);
$scope.Infants.push(value);
});
});
}
Then the function call when the ""Add" button is clicked looks like this:
$scope.AddProfile = function () {
// get the firebase location
var newInfantRef = firebase.database().ref('/users/' + firebaseUser.uid + '/infantList/');
// create the element
var newRef = newInfantRef.push();
//add attributes
var newItem = {
riskLevel: '1.0'
, ID: newRef.key
, name: "Reggie"
, gender: "M"
, DOB: "2015-02-01"
};
// Write the new infant.
var newInfant = {};
newInfant['/' + newRef.key + '/'] = newItem;
newInfantRef.update(newInfant);
}
In your InfantList.on() you are pushing again all values to the array when a new value is added.
To solve this try:
InfantList.on('child_added', snapshot => {
...your things...
}
This only push the new value to the array when the new value is added.

Resources