Display multi-reference field in repeater

I have a Product collection that has several multi-reference fields for the product attributes (‘material’, ‘certification’, etc). Each of these fields has its own collection. I want to list the Product in a repeater including all the reference fields. Following the movies/actors and songs/singers tutorial I manage to display the products, HOWEVER the multi-reference fields only shows 1 value even when that product has several values in that field. For example, if the ‘material’ column has ‘Bamboo’ & ‘Plastic’ in the ‘Product’ collection, the repeater only shows ‘Bamboo’. Same goes for the other multi-reference fields.

This is how I set up the dynamic page:

  1. Connect the repeater to the dynamic dataset ‘Product’. Connect elements such as image, title, etc to the corresponding fields in the ‘Products’ collection.

  2. Add a dataset ‘#dsMaterial’, connect it to ‘Material’ collection, set the Filter as follows:
    Field: Product (Product) – this is the automatic field created by Wix in ‘Material’ collection
    Condition: Includes
    Dataset: Product
    3. Add a text element, connect it to the ‘#dsMaterial’, connect the text to the ‘Title’ field.

Is there anything missing in my set up? Since a multi-refence fields contains an array, should I do some processing to convert this array to string? If yes, how do I grab the array value? ‘itemData’ variable inside the repeater’s onReady() doesn’t have this data.

Yes, it can be done, take a look at this: .includes()

Thanks for your reply, Bruno.

I have several of these attributes like ‘material’, ‘certification’, ‘category’, ‘options’, etc. Using .include() means I have to use wixData.query() for each of them (please see below paragraph) and this slows down the page loading significantly as everything has to happen in $w.onReady().

I have tried both fetching all the attributes first and store them in variables (array of objects) to reduce database calls, and also try fetching each attribute within the repeater item’s onItemReady(). Unfortunately both ways mean 5-10 seconds of staring at blank page until the data populate the repeater.

That’s why I’m trying to use dataset. It definitely loads faster BUT as I mentioned in my original post, it only fetch the first item from the array :frowning:

I am sorry that I cannot understand what is happening with your setup, but if use this code in my test page, I have access to all the referenced items.

async function getData() {
    const result = await wixData.query("Items1")
    .include("reference")
    .find()
    return result.items
}

Hi there …

Multi-reference fields can significantly slow down your search performance as you first need to query the item itself to get its details, then query the multi-referenced fields one at a time.

Here’s an example: If we have a collection called " Artists ", and a multi-reference field with an ID of " Songs ", here’s how you should get the whole artist object (including his songs).

return wixData.query('Artists').find().then((x) => {
   const artists = [];
   
   x.items.forEach(artist => {
        const artist.songs = await wixData.queryReferenced('Artists', artist._id, songs_ref);
        artists.push(artist);
    })
    
    return artists;
});

As you can see, each item will send an individual XHR request to query the collection, so the more items you have in the query the slower the function will be, and that’s not even the worst, if you’re running this function on the backend, you might get away with setting a limit of 5 items per query, but you’ll probably get a timeout error, meaning your function might not even settle.

However, I have a solution for this, it’s not the easiest, but it gets the job done, instead of saving the IDs of your referenced items in a multi-referenced field, I personally find it easier to have an array field, and store all the IDs inside of it, and when you get the artist item from the query, just run a Promise.all() on the IDs with a get function for each item, this will save you time, but not a lot of it.

const artist = // Something from the query.
artist.songs_ref; // Array field;

// A function to get the referenced item by passing its ID as an argument
const getItems = (id) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve(wixData.get('Songs', id)), 100)
    })
    
    // The reason I used a timeout is to avoid exceeding the maximum stack size
    // The timeout helps reset the stack.
}

// Create an array of promises
const promises = artist.songs_ref.map(getItems);

// Run the promises at the same time to save time.
artist.songs = await Promise.all(promises);

console.log(artists.songs); // [{ _id: 'something', name: 'Song Name' }, {}, .. ]

Of course, you can nest this code inside a forEach( ) loop, loop through all the artists, and get their songs.


Hope this helps~!
Ahmad

Thanks, Ahmad for a very detailed reply! I did something similar to your solution, e.g using Promise.all() to fetch each of the product attributes. I fetch all the attributes first before looping through each Product to reduce the number of database calls (each attribute collection only has ~10 records). Here is a snippet of the workflow:

export async function buildRepeaterData () {
let productList ;
let data = [];
let brands = [], materials = [], certs = [], cats = [];

// Get all products here 
productList  =  **await**  getProducts (); 

**if**  ( productList  &&  productList . length  >  0 ) { 
    **let**  prod ; 
    // Get all attributes, store in array 
    [ brands ,  materials ,  cats ,  certs ] =  **await**  Promise . all ([ 
        getPropertyList ( 'brand' ), 
        getPropertyList ( 'category' ), 
        getPropertyList ( 'certification' ), 
        getPropertyList ( 'material' ) 
    ]); 

    **for**  ( **var**  i  =  0 ;  i  <  productList . length ;  i ++) { 
        prod  =  productList [ i ]; 

        // Find attributes for this product 
        **let**  b  =  brands . find ( o  =>  o . _id  ===  prod . _id ); 
        **let**  cat  =  cats . find ( o  =>  o . _id  ===  prod . _id ); 
        **let**  cer  =  certs . find ( o  =>  o . _id  ===  prod . _id ); 
        **let**  m  =  materials . find ( o  =>  o . _id  ===  prod . _id ); 

        data . push ( { 
            '_id' :  prod . _id ,  
            'title' :  prod . title ,  
            'size' : ( prod . size  ?  prod . size  :  '-' ),  
            'image' :  prod . image , 
            'brand' :  b . value , 
            'category' :  cat . value , 
            'certification' :  cer . value , 
            'material' :  m . value , 
            'finishCertified' : ( prod . finishCertified  ?  **true**  :  **false** ), 
            'hotUse' : ( prod . hotUse  ?  **true**  :  **false** ), 
            'microwave' : ( prod . microwave  ?  **true**  :  **false** ), 
            'oven' : ( prod . oven  ?  **true**  :  **false** ),  
            'freezer' : ( prod . freezer  ?  **true**  :  **false** ), 
            'qldBan' : ( prod . qldBan  ?  **true**  :  **false** ), 
            'saBan' : ( prod . saBan  ?  **true**  :  **false** ) 
            } ); 
    } 
} 
**return**  data ; 

}

However, the page still loads too slow with only 14 sample Products. I don’t think it will be workable as we’ll have more than 200 Products eventually. I may have to do this outside of Wix :frowning:

Thanks, Bruno! Perhaps the diagram below conveys my problem better.

I have tried fetching using code (see my reply to Ahmad below) but it wasn’t workable in the sense that I CAN display the data but page loading takes 5-10 seconds with only 14 sample products (we’ll have more than 200 live products).

What I’m trying to do now is connecting text elements to datasets in the hope that I can display the correct data WITHOUT using code. However by now it seems that it’s not possible too :frowning: I’m running out of idea …

@battra Ok, if you are going to use all the referenced fields, it is going to be slower. I made a site to compare all methods of query for 200 items, but only 23 have 1 or more references.

.queryReferenced() is TOO slow, it is not even usable. It has to go one by one and retrieve the information with each dataset call.

The best method I found was using the .include() method. Here is the code:

async function getIncludeData() {
    let query = await wixData
        .query("Coffees")
        .limit(50)
        .include("multiReference")
        .find()
    let allItems = query.items
    while (query.hasNext()) {
        query = await query.next()
        allItems = allItems.concat(query.items)
    }
    return allItems
}

You can test the performance of different queries here:
Query Performance Tester

It is interesting that in Preview Mode some queries are faster, in others, Published Mode is faster.

@bwprado Which type of object are you using to display every referenced value at the front end. Thanks

@youge if you test the website I created to time this queries, you can see in the console the resulting data.

Query Performance Tester

bro can give your original port link

I am not a developer, is there also a way to show more than 1 value in a multi-reference field without using code?

2 Likes

There doesn’t seem to be a way to do it, I’ve tried so many times. I managed to get Wix Velo to work but it’s very unstable and slow. I couldn’t do it in the editor without using code because there is no feature to add Repeater into Repeater. This is actually a must in programming, I don’t understand how it can’t be done, we should be able to put repater into repater when working with CMS.