Custom Image Component with Masonry Layout (updated)

Hello Retool Community!

I’ve been diving headfirst into custom components in Retool, and it’s been quite an adventure! I don’t often get the chance to code like this outside of work, so I decided to push myself and learn how to connect to Retool components—like modals and queries—through JavaScript (thanks @AJVancattenburch for some guidance).

One feature I’ve always wanted to implement in Retool is a masonry layout for images. It’s tricky to customize the default image grid to get that Pinterest-style layout. I was hesitant to use the custom component at first since I hadn’t worked with it before, and I wasn’t sure how to connect it to other components. After many hours of trial and error, I finally managed to put together a working beta! It’s still in development, but I’m excited to share this initial breakthrough. The layout is also reactive, so it dynamically adjusts as new images are added.

I’m sure there are more efficient ways to achieve this, and my code might be a bit rough around the edges since I’m a bit rusty. But hey—it works! I’m currently using a Postgres database to store my data and fetching images from the Unsplash API.

Current Implementation

Here’s a quick overview of what I’ve built so far:

  • Model: This grabs images and IDs, which I then map together in a card-like fashion using the custom component’s iFrame code.
  • Selected: This stores the ID of the image that’s selected. When you click an image, it triggers a query to fetch the corresponding row from my database.
  • Modal Visibility: This controls whether the modal pops up or not. I had trouble targeting the modal directly, so I used a simple boolean to trigger it.

What’s Next?

I’m planning to continue refining the app over the coming weeks. Feel free to take a look at the code and offer suggestions! I’m still learning the best practices for custom components and would appreciate any feedback or pointers.

Looking forward to hearing your thoughts!

Model

{
  "images": {{ getAllKeeps.data.img }},
  "ids": {{ getAllKeeps.data.id }},
  "selected": {{ null }},
  "modalVisibility": false
}

IFrame

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Image Gallery with Retool Integration</title>
    <style>
        body, html {
            margin: 0;
            padding: 0;
            font-family: Arial, sans-serif;
        }
        .gallery {
            column-count: 3; 
            column-gap: 16px; 
            padding: 16px;
        }
        .gallery-item {
            display: inline-block; 
            width: 100%; 
            margin-bottom: 16px; 
            border-radius: 8px;
            cursor: pointer;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
            transition: transform 0.3s ease;
        }
        .gallery-item img {
            width: 100%;
            height: auto; 
            display: block; 
        }
        .gallery-item:hover {
            transform: scale(1.05);
        }
    </style>
</head>
<body>
    <div id="react"></div>

    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>

    <script type="text/babel">
        const ImageGallery = ({ model }) => {
            if (!model.images || model.images.length === 0) {
                return <p>No images available.</p>;
            }

const handleClick = (id) => {
    if (window.Retool) {
        window.Retool.modelUpdate({ selected: id })
        window.Retool.triggerQuery('getKeepData', { id });
        window.Retool.modelUpdate({ modalVisibility: true });
    } else {
        console.log('Retool context is not available.');
    }
};
          
            return (
                <div className="gallery">
                    {model.images.map((image, index) => (
                        <div className="gallery-item" key={index} onClick={() => handleClick(model.ids[index], image)}>
                            <img src={image} alt={`Image ${index}`} />
                        </div>
                    ))}
                </div>
            );
        };

        const ConnectedComponent = Retool.connectReactComponent(ImageGallery);
        const container = document.getElementById('react');
        const root = ReactDOM.createRoot(container);
        root.render(<ConnectedComponent />);
    </script>
</body>
</html>

You may have seen my previous post on this. This is the better post :smiley:

5 Likes


Here is a screenshot of the main page (more pages to be designed soon).

3 Likes

Thomas this is so cool! Can't get wait to take a look with you in person later! :star_struck:

1 Like