Discussion on getting an array of images from Sanity for a gallery page in Next.js, with tips on code formatting and content strategy.
34 replies
Last updated: Jan 5, 2023
J
Hello Happy New Year! I am trying to get an array of URL's from Sanity but either get null or object.
export const getStaticProps = async () => { const query = groq`*[_type == "gallery" ]{ "url": imgUrl.asset->url, title, description, projectLink, codeLink, tags }` const data = await client.fetch(query) return { props: { data } } }
Dec 31, 2022, 8:07 PM
K
In the request you are asking for a single image url not an array.
Dec 31, 2022, 11:02 PM
K
I'm not positive but try <nameOfArray>[] -> {"url": ...
Dec 31, 2022, 11:08 PM
K
Post the gallery schema here and I can give you a better idea.
Dec 31, 2022, 11:28 PM
J
I don't think that you saw all of the code my bad here you go
Jan 1, 2023, 12:50 AM
J
import groq from "groq";import imageUrlBuilder from "@sanity/image-url";
import client from "../client";
import React, { useState } from 'react';
import Masonry, {ResponsiveMasonry} from "react-responsive-masonry"
import styles from "../styles/Gallery.module.css";
import { setRevalidateHeaders } from "next/dist/server/send-payload";
import { data } from "jquery";
// get the image url from the sanity asset
function urlFor(source) {
return imageUrlBuilder(client).image(source);
}
//end get the image url
//query get sanity data
export const getStaticProps = async () => {
const query = groq`*[_type == "gallery" ]{
"url": imgUrl.asset->url,
title,
description,
projectLink,
codeLink,
tags
}`
const data = await client.fetch(query)
return {
props: { data }
}
}
// end get data exported as 'data'
// the page magic
const Gallery = ({ data }) => {
// create an array of images to use for prev and next
const images = data.map(({ url }) => [url])
console.log(images)
// end create an array
const [info, setInfo] = useState({img: '', i: 0})
const viewImage = (img, i)=>{
setInfo({img, i})
}
const imgAction = (action) => {
let i = info.i
if(action === 'next-img'){
setInfo({img: images[i + 1], i: i + 1})
}
if(action === 'prev-img'){
setInfo({img: images[i - 1], i: i - 1})
}
if(!action){
setInfo({img: '', i: 0})
}
}
return (
<>
{info.img &&
<div style={{
width: '100%',
height: '100vh',
background: 'black',
position: 'fixed',
top: '0',
left: '0',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden',
}}>
<button onClick={()=> imgAction()} style={{position: 'absolute', top: '20px', right: '20px'}}>X</button>
<button onClick={()=> imgAction('prev-img')} style={{marginRight: '10px' }}>Previous</button>
<img src={info.img} style={{width: 'auto', maxWidth: '90%', maxHeight: '90%'}}/>
<button onClick={()=> imgAction('next-img')} style={{marginLeft: '10px' }}>Next</button>
</div>
}
<ResponsiveMasonry
columnsCountBreakPoints={{350: 1, 750: 2, 900: 3}}
>
<Masonry gutter="20px">
{ data.map((p, i)=>(
<img
key={i}
src={p.url}
alt={p.title}
className={styles.pic}
onClick={()=> viewImage(p.url, i)}
/>
))}
</Masonry>
</ResponsiveMasonry>
</>
)
}
export default Gallery
import client from "../client";
import React, { useState } from 'react';
import Masonry, {ResponsiveMasonry} from "react-responsive-masonry"
import styles from "../styles/Gallery.module.css";
import { setRevalidateHeaders } from "next/dist/server/send-payload";
import { data } from "jquery";
// get the image url from the sanity asset
function urlFor(source) {
return imageUrlBuilder(client).image(source);
}
//end get the image url
//query get sanity data
export const getStaticProps = async () => {
const query = groq`*[_type == "gallery" ]{
"url": imgUrl.asset->url,
title,
description,
projectLink,
codeLink,
tags
}`
const data = await client.fetch(query)
return {
props: { data }
}
}
// end get data exported as 'data'
// the page magic
const Gallery = ({ data }) => {
// create an array of images to use for prev and next
const images = data.map(({ url }) => [url])
console.log(images)
// end create an array
const [info, setInfo] = useState({img: '', i: 0})
const viewImage = (img, i)=>{
setInfo({img, i})
}
const imgAction = (action) => {
let i = info.i
if(action === 'next-img'){
setInfo({img: images[i + 1], i: i + 1})
}
if(action === 'prev-img'){
setInfo({img: images[i - 1], i: i - 1})
}
if(!action){
setInfo({img: '', i: 0})
}
}
return (
<>
{info.img &&
<div style={{
width: '100%',
height: '100vh',
background: 'black',
position: 'fixed',
top: '0',
left: '0',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden',
}}>
<button onClick={()=> imgAction()} style={{position: 'absolute', top: '20px', right: '20px'}}>X</button>
<button onClick={()=> imgAction('prev-img')} style={{marginRight: '10px' }}>Previous</button>
<img src={info.img} style={{width: 'auto', maxWidth: '90%', maxHeight: '90%'}}/>
<button onClick={()=> imgAction('next-img')} style={{marginLeft: '10px' }}>Next</button>
</div>
}
<ResponsiveMasonry
columnsCountBreakPoints={{350: 1, 750: 2, 900: 3}}
>
<Masonry gutter="20px">
{ data.map((p, i)=>(
<img
key={i}
src={p.url}
alt={p.title}
className={styles.pic}
onClick={()=> viewImage(p.url, i)}
/>
))}
</Masonry>
</ResponsiveMasonry>
</>
)
}
export default Gallery
Jan 1, 2023, 12:50 AM
S
user T
are you using React or NextJS?Jan 2, 2023, 1:43 PM
S
Do you have gallery document type? Or where is the gallery setup in your schemas?
Jan 2, 2023, 1:44 PM
S
This tells me youβre using next, but there are some inconsistencies in your code, I would tackle π
1) You are querying for a document called gallery, but only query for one imgUrl. Without knowing more about your setup this will only work (see comment π)
I would recommend using the Image functionality of
NextJs and Sanity to get responsive and optimised images.
2) using the
optimised image functionality (
3) This is a more friendly tip, but very important in my opinion:
Using verbose vars in your code will help you maintain it and make scaling way easier π. So instead of this:
you could write this:
Following good practices like this will make your code better searchable and readable for both machines and people
π€ (believe me, using Machine Learning and AI in code like Github Copilot shows how important this is π )
I will wait for you to provide some answers, if you need more help
1) You are querying for a document called gallery, but only query for one imgUrl. Without knowing more about your setup this will only work (see comment π)
export const getStaticProps = async () => { const query = groq`*[_type == "gallery" ]{ "url": imgUrl.asset->url, // This would work for getting image urls directly "imageUrls": images[].image.asset->url, title, description, projectLink, codeLink, tags }` const data = await client.fetch(query) return { props: { data }, } }
NextJs and Sanity to get responsive and optimised images.
2) using the
optimised image functionality (
image-urlplus
<Image/>) where you combine these 2 instructions from NextJs and Sanity
3) This is a more friendly tip, but very important in my opinion:
Using verbose vars in your code will help you maintain it and make scaling way easier π. So instead of this:
const [info, setInfo] = useState({ img: '', i: 0 })
const [displayedImage, setDisplayedImage] = useState({ imageUrl: '', imageIndex: 0 })
π€ (believe me, using Machine Learning and AI in code like Github Copilot shows how important this is π )
I will wait for you to provide some answers, if you need more help
Jan 2, 2023, 1:58 PM
J
That's great! I really appreciate the advice and it is always welcome. I am positive that there is a more efficient way to code this page so any help is great. I would like to have a galley page that I can use in several projects. You're the best!:sanity_with_3_hearts:
Jan 2, 2023, 5:56 PM
S
So then one Gallery with the same images across multiple other pages? then that will work in a way. Where do you store the other data for the pages?
Jan 2, 2023, 5:58 PM
J
No it would be a page that uses data (images and content) stored in Sanity. The page would work the same given the schema and names all don't change. That is easy though. The project id in sanity would really be the only change to the Next and Sanity apps. Images for other pages that are a part of the content are stored in the body (portable text). Ultimately yes all data is in Sanity. I also am working on a dynamic menu for restaurants so that updates would be easy which has it's own schema / images in the same site.
Jan 2, 2023, 7:11 PM
S
I was asking about your schema setup π I mean HOW will the content in sanity look like and how will it translate into a front-end. I have no idea how your code looks like, this is why I ask.There are many roads that lead to rome
π£οΈ
As a small tipp number 2:
You need to plan your content strategy early on (structure your content), or it will bite you in the
π when you scale up and then need to mutate all you data. Our Carrie Hanes is a pro in all things content and I think this might help you longterm
π£οΈ
As a small tipp number 2:
You need to plan your content strategy early on (structure your content), or it will bite you in the
π when you scale up and then need to mutate all you data. Our Carrie Hanes is a pro in all things content and I think this might help you longterm
Jan 2, 2023, 7:22 PM
J
Ah ok. Here's the gallery.js:
Jan 2, 2023, 7:55 PM
J
export default { name: 'gallery', title: 'Gallery', type: 'document', fields: [ { name: 'title', title: 'Title', type: 'string', }, { name: 'description', title: 'Description', type: 'string', }, { name: 'projectLink', title: 'Project Link', type: 'string', }, { name: 'codeLink', title: 'Code Link', type: 'string', }, { name: 'imgUrl', title: 'ImageUrl', type: 'image', options: { hotspot: true, }, }, { name: 'categories', title: 'Tags', type:'array', of: [ { type: 'reference', to: {type: 'category'} } ] }, ], };
Jan 2, 2023, 7:55 PM
S
Tip number 3: use code formatting in Slack: (see left image)1. Short inline code snippets </> like this
3. if you need to share whole files: cmd+shift+enter
And you can edit your posted messages (see right image)
It is really hard to work through unformatted text/code+
const = xis useful (cmd+shift+c)2. If you need to share longer snippets use block code (option+shift+c)
3. if you need to share whole files: cmd+shift+enter
And you can edit your posted messages (see right image)
It is really hard to work through unformatted text/code+
Jan 2, 2023, 7:58 PM
S
What is
codeLink? what is
projectLink?And what else are you using for data in the pages (do you have a page document type in sanity? β¦
Jan 2, 2023, 8:01 PM
J
Those links are for a dev portfolio piece so perhaps a website or github. That part is really just a placeholder
Jan 2, 2023, 8:04 PM
S
okay, I will wait for you to write what you have setup. I cannot continue guessing πYou show me how you set things up, and I will show you how to use the data. I am here to help, but if you dont read and answer my whole questions, I cannot help you
π₯²
π₯²
Jan 2, 2023, 8:22 PM
S
But now that i can read your code: You have a gallery with only one image?And I would not call it imageUrl, since you dont get an image url but a reference to an image, which in your frontend queried data will be an object.
Jan 2, 2023, 8:25 PM
S
I would set it up like this:
{ name: 'images', title: 'Images', type:'array', of: [ { type: 'image', name: 'image', options: { hotspot: true, }, } ] },
Jan 2, 2023, 8:27 PM
J
so the groq would need to change and that is where I get stuck. The code needs a 'typical' map to show each clickable image which opens the modal. It is because the images are needed in an array that I got so confused... I made the changes you suggested in the schema... now what on the query?//query get sanity data
export const getStaticProps = async () => { const query = groq`*[_type == "gallery" ]{ "url": imgUrl.asset->url, title, description, projectLink, codeLink, tags }` const data = await client.fetch(query) return { props: { data } } } // end get data exported as 'data'
Jan 2, 2023, 9:56 PM
J
the use case here is say a tattoo artist wants to showcase some of their work on the gallery page with a larger version in a modal
Jan 2, 2023, 9:58 PM
J
no need for more than one image per 'gallery entry
Jan 2, 2023, 9:58 PM
S
A gallery is always a collection of images, you cannot map over an object which you now are trying to do.
Jan 2, 2023, 10:07 PM
S
ARRAY.map π
Jan 2, 2023, 10:07 PM
S
collection = list = array, you are setting up an object though.
Jan 2, 2023, 10:09 PM
S
If you use this:
your query:
you need to then in your front-end use the
image-url functionality
using this you will have a whole image object with the imageUrl added to it. You can also skip constructing the new object and using it like to in the component:
{ name: 'images', title: 'Images', type:'array', of: [ { type: 'image', name: 'image', options: { hotspot: true, }, } ] },
const query = groq`*[_type == "gallery" ]{ images, title, description, projectLink, codeLink, tags }`
image-url functionality
urlFor(source)when mapping over the images array:
// in the component where you pass down images in props with a configured urlFor image from @sanity/image-url const images = props?.images.map((image, imageIndex) => { const imgUrl = urlFor(image.asset).width(300).url() return image['imageUrl']=imgUrl })
src={urlFor(image.asset).width(300).url()}
Jan 2, 2023, 10:19 PM
J
Oh wow ok that makes sense! Much cleaner way of doing this. Perfect thank you so much!
Jan 2, 2023, 10:25 PM
J
I am still trying to get this to work as you showed using the entire image object which makes sense that is how I used it to begin with but I am getting TypeError: Cannot read properties of undefined (reading 'map')
Jan 5, 2023, 8:55 PM
J
does any of this have to do with using the app folder in Next?
Jan 5, 2023, 8:55 PM
S
Thats all to do with your code ;) you need to check that you are passing down data and where the bottle neck is.
Jan 5, 2023, 9:09 PM
S
?
Jan 5, 2023, 9:09 PM
J
Actually I just got it. :)
Jan 5, 2023, 9:09 PM
J
typo
Jan 5, 2023, 9:10 PM
Sanityβ build remarkable experiences at scale
Sanity is a modern headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.