Currently building my portfolio site using Next.js and Sanity for my CMS. I've managed to pull the featured image for a project using the useEffect and setImageUrl method. I'm trying to pull images to display on the front end for different sections on the project details page. These are images I uploaded to my database through sanity. I end up getting the broken image icon instead of the actual image. I'm pretty stumped on why it's not pulling the image data correctly. Here is the code for the project single page (as a slug).
import imageUrlBuilder from '#sanity/image-url'
import { useState, useEffect } from 'react';
import BlockContent from '#sanity/block-content-to-react'
export const Post = ({ title, image, problem, solution, role, body, body2, body3, body4, body5, photo }) => {
const [imageURL, setImageUrl] = useState('');
useEffect(() => {
const imgBuilder = imageUrlBuilder({
projectId: 'hidden',
dataset: 'production',
});
setImageUrl(imgBuilder.image(image));
}, [image]);
return (
<div>
<div className="container mx-auto mt-8 mb-20 px-6">
{imageURL && <img className="my-8" src={imageURL} />}
<h1 className="text-5xl font-bold my-8">{title}</h1>
<div className="flex justify-between space-x-12 mb-8">
<div className="flex flex-col">
<h3 className="text-lg font-semibold">The Problem</h3>
<div className="w-auto my-2">
<BlockContent blocks={problem} />
</div>
</div>
<div className="flex flex-col">
<h3 className="text-lg font-semibold">The Solution</h3>
<div className="w-auto my-2">
<BlockContent blocks={solution} />
</div>
</div>
<div className="flex flex-col">
<h3 className="text-lg font-semibold">Role</h3>
<div className="w-auto my-2">
<BlockContent blocks={role} />
</div>
</div>
</div>
<img src={photo} />
<div className="w-auto">
<BlockContent blocks={body} />
</div>
<div className="w-auto">
<BlockContent blocks={body2} />
</div>
<div className="w-auto">
<BlockContent blocks={body3} />
</div>
<div className="w-auto">
<BlockContent blocks={body4} />
</div>
<div className="w-auto">
<BlockContent blocks={body5} />
</div>
</div>
)
};
export const getServerSideProps = async pageContext => {
const pageSlug = pageContext.query.slug;
if (!pageSlug) {
return {
notfound: true
}
}
const query = encodeURIComponent(`*[ _type == "project" && slug.current == "${pageSlug}"]`) ;
const url = `https://hidden?query=${query}`;
const result = await fetch(url).then(res=> res.json());
const post = result.result[0];
if(!post) {
return{
notfound: true
}
} else {
return {
props: {
title: post.projectTitle,
image: post.projectImage,
problem: post.problem,
solution: post.solution,
role: post.role,
body: post.sectionOne,
body2: post.sectionTwo,
body3: post.sectionThree,
body4: post.sectionFour,
body5: post.sectionFive,
photo: post.Image01
}
}
}
};
export default Post;
instead of using a deconstructor for your imageUrl, try with the following syntax:
import sanityClient from "../../lib/client";
import imageUrlBuilder from "#sanity/image-url";
const builder = imageUrlBuilder(sanityClient);
function urlFor(source) {
return builder.image(source);
}
{item.image && (<img src={urlFor(item.image).width(220).url()} alt={item.title} />)}
where sanityClient.js in React:
https://www.sanity.io/guides/create-a-single-page-application-with-react-and-sanity
and in Next.js:
https://www.sanity.io/guides/sanity-nextjs-tailwindcss
Related
I'm passing all the user's data to the card component, but I want to remove the card when I click the button, without rendering the button more than one time. Is it possible to achieve that?
The cards are stacked on top of each other.
Thanks in advance!
This is where I'm getting the data and controlling the button click
const [user, setUser] = React.useState(null)
const [selectedUser, setSlectedUser] = React.useState(0)
const getUsers = async () => {
try{
const response = await axios.get('http://localhost:8000/users')
setUser(response.data)
console.log(response.data)
}
catch(err){
console.log(err)
}
}
useEffect(()=>{
getUsers()
}, [])
const handleCardClick = (userId) => {
setSlectedUser(userId)
}
const handleRemove = () => {
setUser(user.filter((user)=> user.userId !== selectedUser))
}
And this is where I'm rendering it.
<div>
{user && user.map(user => (
<div>
<Card
country={user.country}
name={user.name}
about={user.about}
photo={user.photo}
onClick={() => handleCardClick(user.userId)}/>
</div>
))}
<button className='btn-cards text-center' onClick={handleRemove}>DELETE</button>
</div>
This is the card component
import React from 'react'
const Card = ({name, about, photo, country}) => {
return (
//create a card to display the user's profile
<div className='bg-white rounded-3xl shadow-lg p-6 mb-4 card'>
<div className='flex flex-row justify-between'>
<div className='flex flex-col'>
<img className='rounded-2xl w-96 h-96 object-cover' src={photo} alt="" />
<h1 className='text-2xl'>{name}</h1>
<h1 className='text-xl'>{country}</h1>
<h2 className='text-xl'>{about}</h2>
</div>
</div>
</div>
)
}
export default Card
The state:
// In this stae var you will save the selected user ID
const [selectedUser, setSlectedUser] = useState(0)
The handlers:
const handleCardClick = (userId) => {
setSlectedUser(userId)
}
const handleRemove = () => {
setUser(user.filter((user)=> user.userId !== selectedUser))
}
The card item inside the list:
<Card
country={user.country}
name={user.name}
about={user.about}
photo={user.photo}
onClick={() => handleCardClick(user.userId)}/>
The button, in whatever place you like:
<button className='btn-cards text-center' onClick={handleRemove}>DELETE</button>
By the way your root 'div' in the list needs a key, I suggest to use the user's id: <div key={user.userId}>
Card component receiving the onClick method as a props:
const Card = ({name, about, photo, country, onClick}) => {
return (
//create a card to display the user's profile
<div className='bg-white rounded-3xl shadow-lg p-6 mb-4 card' onClick={onClick}>
<div className='flex flex-row justify-between'>
<div className='flex flex-col'>
<img className='rounded-2xl w-96 h-96 object-cover' src={photo} alt="" />
<h1 className='text-2xl'>{name}</h1>
<h1 className='text-xl'>{country}</h1>
<h2 className='text-xl'>{about}</h2>
</div>
</div>
</div>
)
}
I have displayed all the anime images from api, now I want if any anime result is clicked then it should open details for that particular anime. I have created component Details where I want to send details of anime.
how can I send details of anime to the component Details ?
Here is my code:
import { useEffect, useState } from 'react';
import './CSS/style.css';
import Details from './components/Details';
function App() {
const [user, setUser] = useState([])
const getUsers = async () =>{
const response = await fetch('https://ghibliapi.herokuapp.com/films')
setUser(await response.json());
}
useEffect(() => {
getUsers();
}, []);
const getDetail = (e) =>{
// what should I write here to pass props on component < Details />
console.log(e)
}
return (
<>
<h2>Anime World</h2>
<div className="container-fluid mt-5">
<div className="row text-center">
{
user.map((curElem) => {
return (
<div className="col-10 col-md-4 mt-5" key={curElem.id} >
<div className="card">
<img src={curElem.image} className="card-img-top imageSize" alt="..."/>
<div className="card-body">
<h5 className="card-title">{curElem.title} </h5>
<button onClick={getDetail} >Get Detail</button>
</div>
</div>
</div>
)
})
}
</div>
</div>
</>
);
}
export default App;
Dummy values are given to component Details right now, I have to get correct value from props.
import React from 'react';
import './details.css';
function Details() {
return (
<>
<h2>Information of Anime</h2>
<div className="container-fluid mt-5">
<div className="row text-center">
<div className="col-10 col-md-8 mx-auto" >
<div className="card box">
<img src= '...' className="card-img-top imageSize" alt="..."/>
<div className="card-body">
<div className='details'>
<h5><span>Title:</span> Title</h5>
<h5><span>Original Title:</span> Original Title</h5>
<h5><span>Director:</span> Director</h5>
<h5><span>Producer:</span> Producer</h5>
<h5><span>Release Date:</span> date</h5>
<h5><span>Running time:</span> time</h5>
<h5><span>Description:</span> <p> Description</p></h5>
</div>
</div>
</div>
</div>
</div>
</div>
</>
)
}
export default Details;
I'm going to assume that the list you receive from the API is the list of each anime, and when you click you want to render the selected anime using the Details component. If that's the case this is my approach
import { useEffect, useState } from 'react';
import './CSS/style.css';
import Details from './components/Details';
function App() {
const [user, setUser] = useState([]);
const [anime, setAnime] = useState(null);
const getUsers = async () =>{
const response = await fetch('https://ghibliapi.herokuapp.com/films');
setUser(await response.json());
};
useEffect(() => {
getUsers();
}, []);
const getDetail = (anime) =>{
setAnime(anime);
};
return (
<>
<h2>Anime World</h2>
<div className="container-fluid mt-5">
<div className="row text-center">
{user.map((elem) => (
<div className="col-10 col-md-4 mt-5" key={curElem.id}>
<div className="card">
<img src={elem.image} className="card-img-top imageSize" alt="..."/>
<div className="card-body">
<h5 className="card-title">{elem.title} </h5>
<button onClick={() => getDetail(elem)} >Get Detail</button>
</div>
</div>
</div>
))}
</div>
</div>
{anime && <Details {...anime} />}
</>
);
}
export default App;
So basically what I'm doing here is rendering <Details /> only when an anime element is in state. If it's null it won't render the component.
Secondly I'm spreading the selected element into the Details component, so that inside the component you can access each property like this:
import React from 'react';
import './details.css';
function Details(props) {
const { title } = props; // each prop is each property of the selected anime
return (
<>
<h2>Information of Anime</h2>
<div className="container-fluid mt-5">
<div className="row text-center">
<div className="col-10 col-md-8 mx-auto" >
<div className="card box">
<img src= '...' className="card-img-top imageSize" alt="..."/>
<div className="card-body">
<div className='details'>
<h5><span>Title:</span> {title}</h5>
<h5><span>Original Title:</span> Original Title</h5>
<h5><span>Director:</span> Director</h5>
<h5><span>Producer:</span> Producer</h5>
<h5><span>Release Date:</span> date</h5>
<h5><span>Running time:</span> time</h5>
<h5><span>Description:</span> <p> Description</p></h5>
</div>
</div>
</div>
</div>
</div>
</div>
</>
)
}
export default Details;
I am working on making a blog using React and Sanity, and I want to display the categories associated with each post underneath each post. I am able to display the categories, but it displays all categories, not just the ones associated with each specific post. How can I make it so each posts displays only the categories associated with it?
Here is my code:
import React, { useEffect, useState } from 'react'
import client from '../client'
import BlockContent from '#sanity/block-content-to-react'
import { Link } from 'react-router-dom'
function Main() {
const [posts, setPosts] = useState([])
useEffect(() => {
client.fetch(
`*[_type == "post"] {
title,
slug,
body,
author,
mainImage {
asset -> {
_id,
url
},
alt
},
publishedAt,
"categories": categories[]->title
}`
).then((data) => setPosts(data))
.catch(console.error)
}, [])
return (
<div className='grid lg:grid-cols-3 md:grid-cols-2 gap-8 m-4 '>
<div>
{posts.slice(0, 1).map((p, i) => (
<Link to = {`/blog/${p.slug.current}`} className=''>
<article key = {p.slug.current} className=''>
<img src = {p.mainImage.asset.url} alt = {p.title} className='' />
<div>
<p className='font-bold text-xl text-secondary'>{p.title}</p>
<div className=''>
<p className='text-sm'>By Brandon Pyle | {new Date(p.publishedAt).toLocaleDateString()}</p>
{posts.map((c, i) => (
<p className='inline'>{c.categories}, </p>
))}
</div>
</div>
</article>
</Link>
))}
</div>
<div className='my-[-16px]'>
{posts.slice(1, 4).map((p, i) => (
<Link to = {`/blog/${p.slug.current}`} className='col-start-2'>
<article key = {p.slug.current} className='flex my-4'>
<img src = {p.mainImage.asset.url} alt = {p.title} className='w-auto h-auto max-h-[80px]' />
<div>
<p className='font-bold text-xl text-secondary'>{p.title}</p>
<p className='text-sm'>By Brandon Pyle | {new Date(p.publishedAt).toLocaleDateString()}</p>
<div>
{posts.map((c, i) => (
<p className='inline'>{c.categories}, </p>
))}
</div>
</div>
</article>
</Link>
))}
</div>
</div>
)
}
export default Main
Thanks to #cfm I was able to find a solution:
Your query looks good. But you are mapping posts again inside the .map
of posts. I would expect you to do p.categories.map in the inner map,
if you wanted only this posts categories titles.
As he suggested, I simply switched posts.map to p.categories.map, and this fixed the problem! Here is the full fixed code:
import React, { useEffect, useState } from 'react'
import client from '../client'
import BlockContent from '#sanity/block-content-to-react'
import { Link } from 'react-router-dom'
function Main() {
const [posts, setPosts] = useState([])
useEffect(() => {
client.fetch(
`*[_type == "post"] {
title,
slug,
body,
author,
mainImage {
asset -> {
_id,
url
},
alt
},
publishedAt,
"categories": categories[]->title
}`
).then((data) => setPosts(data))
.catch(console.error)
}, [])
return (
<div className='grid lg:grid-cols-3 md:grid-cols-2 gap-8 m-4 '>
<div>
{posts.slice(0, 1).map((p, i) => (
<Link to = {`/blog/${p.slug.current}`} className=''>
<article key = {p.slug.current} className=''>
<img src = {p.mainImage.asset.url} alt = {p.title} className='' />
<div>
<p className='font-bold text-xl text-secondary'>{p.title}</p>
<div className=''>
<p className='text-sm'>By Brandon Pyle | {new Date(p.publishedAt).toLocaleDateString()}</p>
{p.categories.map((c, i) => (
<p className='inline'>{c}, </p>
))}
</div>
</div>
</article>
</Link>
))}
</div>
<div className='my-[-16px]'>
{posts.slice(1, 4).map((p, i) => (
<Link to = {`/blog/${p.slug.current}`} className='col-start-2'>
<article key = {p.slug.current} className='flex my-4'>
<img src = {p.mainImage.asset.url} alt = {p.title} className='w-auto h-auto max-h-[80px]' />
<div>
<p className='font-bold text-xl text-secondary'>{p.title}</p>
<p className='text-sm'>By Brandon Pyle | {new Date(p.publishedAt).toLocaleDateString()}</p>
<div>
{p.categories.map((c, i) => (
<p className='inline'>{c}, </p>
))}
</div>
</div>
</article>
</Link>
))}
</div>
</div>
)
}
export default Main
I have a react code here
i want to load data from an API but it's taking time to get the data, thus my function is failing
How do i set it that it should wait for the data before rendering
import Head from 'next/head'
import Link from 'next/link'
import Navbar from './Navbar'
import Template from './Template'
import { useState, useEffect } from 'react'
export async function getStaticProps() {
const response = await fetch('https://peegin.com/api/public/peegins/recent')
const data = await response.json()
return {
props: { data }
}
}
const Home = ({ data }) => {
return (
<div className="content">
<Head>
<title>Peegin Recent</title>
</Head>
<Navbar />
{title}
{data.map(peegin => (
<div className="preview" key={peegin.permalink}>
<h3 className="title">
{peegin.title}
</h3>
<p>{peegin.meaning}</p>
<p className="example">Example</p>
<p className="example-content">{peegin.example}</p>
<p className="origin">Origin: {peegin.origin}</p>
<div className="name">
<h4>By</h4> <h4 className="namegreen">{peegin.user.name}</h4> <h4>{peegin.created_at}</h4>
</div>
<p className="views">{peegin.views.view} Views</p>
</div>
))}
</div>
);
}
export default Home;
const Home = ({ data }) => {
// add this as fallback
if(!data){
return <h1>Loading..<h1/>
}
return (
<div className="content">
<Head>
<title>Peegin Recent</title>
</Head>
<Navbar />
{title}
// add this to check for data first then do map
{data && data.map(peegin => (
<div className="preview" key={peegin.permalink}>
<h3 className="title">
{peegin.title}
</h3>
<p>{peegin.meaning}</p>
<p className="example">Example</p>
<p className="example-content">{peegin.example}</p>
<p className="origin">Origin: {peegin.origin}</p>
<div className="name">
<h4>By</h4> <h4 className="namegreen">{peegin.user.name}</h4> <h4>{peegin.created_at}</h4>
</div>
<p className="views">{peegin.views.view} Views</p>
</div>
))}
</div>
);
}
Before this, I'm using Snapshot and try to map it. But it failed for a single document. After I read back the documentations from firebase. They recommend using this way for a single document. But I don't know how to make it readable on the site rather than console. I want to send data on the title and essay.
const { id } = useParams()
useEffect(() => {
db.collection("posts").doc(id).get().then(doc => {
const newData = doc.data();
console.log(newData);
});
}, []);
return (
<div className="log">
<article className="log-details">
<div className="author-pic clearfix">
<img src={profile} alt="" />
</div>
<div className="log-preview">
<div class="cover"></div>
<h2 className="title clearfix">title here</h2>
<div className="details ">
<img src={One} alt="" />
<p className="essay">essay here</p>
</div>
</div>
</article>
</div>
)
To display the post Data use useState and set the postData with setPostData(newData). Then you can read the postData values with {postData.title} and {postData.essay} in the return statement.
Dont forget to import useState with import React, { useState, useEffect } from "react".
const { id } = useParams()
const [postData, setPostData] = useState("");
useEffect(() => {
db.collection("posts").doc(id).get().then(doc => {
const newData = doc.data();
setPostData(newData);
console.log(newData);
});
}, []);
return (
<div className="log">
<article className="log-details">
<div className="author-pic clearfix">
<img src={profile} alt="" />
</div>
<div className="log-preview">
<div class="cover"></div>
<h2 className="title clearfix">{postData && postData.title}</h2>
<div className="details ">
<img src={One} alt="" />
<p className="essay">{postData && postData.essay}</p>
</div>
</div>
</article>
</div>
)