I'm trying to create a single page from the id that is passed as a parameter.
my routes structure:
When I pass the mouse over an item in my list I get the id of the firebase document so I need to create a page to show the data in the documents based on their ids.
http://localhost:3000/category/rent/541KqSMHpU17QuYLihFs
id:
541KqSMHpU17QuYLihFs
My listItem component:
<script>
export let listing
export let id
export let handleDelete
import DeleteIcon from '../../static/assets/svg/deleteIcon.svg'
</script>
<li class="categoryListing">
<a href={`/category/${listing.type}/${id}`} class="categoryListingLink">
<img src={listing.imgUrls[0]} alt={listing.name} class="categoryListingImg" />
<div class="categoryListingDetails">
<p class="categoryListingLocation">
{listing.location}
</p>
<p class="CategoryListingName">
{listing.name}
</p>
<p class="categoryListingPrice">
${listing.offer
? listing.discountedPrice.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.')
: listing.regularPrice.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.')}
{listing.type === 'rent' ? '/ por mês' : ''}
</p>
<div class="categoryListingInfoDiv">
<img src="/assets/svg/bedIcon.svg" alt="cama" />
<p class="categoryListingInfoText">
{listing.bedrooms > 1 ? `${listing.bedrooms} camas` : `${listing.bedrooms} cama`}
</p>
<img src="/assets/svg/bathtubIcon.svg" alt="banheiro" />
<p class="categoryListingInfoText">
{listing.bathrooms > 1
? `${listing.bathrooms} banheiros`
: `${listing.bathrooms} banheiro`}
</p>
</div>
</div>
</a>
{#if handleDelete}
<DeleteIcon
class="removeIcon"
fill="rgb(231, 76, 60)"
onClick={() => {
handleDelete(listing.id, listing.name)
}}
/>
{/if}
</li>
The important thing here is it:
<a href={`/category/${listing.type}/${id}`} class="categoryListingLink">
How do I make my [listingId] slug be the id page?
my [listingId].svelte so far:
<script>
import { page } from '$app/stores'
const listingId = $page.params.listingId
import { db } from '../../../../firebase.config.js'
// get the id parameter from the url
</script>
Happy new Year!!
I had a little trouble understanding your question at first.
As it stands now, your URIs are in the shape /category/id/[listingId], so http://localhost:3000/category/rent/541KqSMHpU17QuYLihFs won't get matched. What you need are URIs in the shape of /category/[id]/[listingId]. So you need to rename your id directory to [id] in order to make it dynamic.
You will then be able to retrieve the id the same way you do listingId:
<script>
import { page } from '$app/stores'
const { id, listingId } = $page.params
import { db } from '../../../../firebase.config.js'
// do stuff
</script>
With the above URL as an example, id will hold the value 'rent' and listingId will hold the value '541KqSMHpU17QuYLihFs'.
Hope this answers your question (and happy new year to you as well!)
Edit: a better, more explicit name for the [id] parameter would be [listingCategory] and would improve the readability of your code/the understanding of your intent.
Related
Such as the Title, I try to create a menu that I use v-for to get array items and keys. Then I need the keys to create the second layer menu.
const localSite = ref('us')
const products = ref({})
const softwares = ref({})
const menuShow = ref({})
const menuTxt = ref({
"us": {
"products": "Products",
"softwares": "Software"
}
})
<div id="menu-wrapper">
<div class="flex">
<div v-for="( item, dataKey ) in menuTxt[localSite]" class="menu-item">
<button class="text-white" :data-category="dataKey" #click="slideDown(dataKey)">
{{ item }}
<font-awesome-icon icon="angle-down" />
</button>
<div class="text-box menu-content">
<p v-for="itemA in dataKey">{{itemA}}</p>
</div>
</div>
</div>
</div>
I think the 'dataKey' is a variable which is the vue data Object.
But I can always get the wrong result.
What shoul I do ?
If I understand your problem correctly, you are trying to access the products and softwares properties by using the dataKey in the template. This is not possible the way you are doing it as it's referring to the key of menuTxt.
I made a StackBlitz to show a different kind of approach. Defining the child menu items along with it's parent in the same object. This also makes it more readable.
I have a page where I fetch data and map through it.
In my map function I display a card component with some data like this:
pokemonsList?.map((pokemon, index) => {
return (
<Link href={`/pokemon/${pokemon.id}`} key={index}>
<a>
<Card pokemon={pokemon} />
</a>
</Link>
);
}
As you can see, the route is dynamic.
What I would like to do is to pass the whole pokemon object to the page.
I would like to achieve this without using the next router query method, because the object contains a lot of data.
Is there an other way ?
You could cache it, either by using some global state management package (Redux, React Query) or inbuilt Context API.
Or
<Link
href={{
pathname: '/pokemon',
query: {
id: pokemon.id,
pokemon: JSON.stringify(pokemon)
}
}}
as={`/pokemon/${pokemon.id}`}
key={index}>
<a>
<Card pokemon={pokemon} />
</a>
</Link>
And then on the page
const { query } = useRouter();
const pokemon = JSON.parse(query.pokemon);
I have a GET request in my home.vue component.
This query allows me to get an array of objects.
To display all the objects, I do a v-for loop and everything works fine.
<div class="commentaires" v-for="(com, index) of coms" :key="index">
My concern is that I want to display an image by clicking on it (coms[index].imageUrl), in a modal (popup).
The modal is displayed fine but not with the correct image, i.e. the modal displays the last image obtained in the loop, which is not correct.
Here is the full code of my home.vue component
<template>
<div class="container">
<div class="commentaires" v-for="(com, index) of coms" :key="index">
<modale :imageUrl="com.imageUrl_$this.index" :revele="revele" :toggleModale="toggleModale"></modale>
<img class="photo" :src=""" alt="image du commentaire" #click="toggleModale">
</div>
</div>
</template>
<script>
//import axios from "axios";
import axios from "axios";
import Modale from "./Modale";
export default {
name: 'HoMe',
data() {
return {
coms: [],
revele: false
}
},
components: {
modale: Modale
},
methods: {
toggleModale: function () {
this.revele = !this.revele;
},
</script>
Here is my modale.vue component
<template>
<div class="bloc-modale" v-if="revele">
<div class="overlay" #click="toggleModale"></div>
<div class="modale card">
<div v-on:click="toggleModale" class="btn-modale btn btn-danger">X</div>
<img :src=""" alt="image du commentaire" id="modal">
</div>
</div>
</template>
<script>
export default {
name: "Modale",
props: ["revele", "toggleModale", "imageUrl"],
};
</script>
I've been working on it for 1 week but I can't, so thank you very much for your help...
in your v-for loop you're binding the same revele and toggleModale to every modal. When there is only one revele then any time it's true, all modals will be displayed. It's therefore likely you're actually opening all modals and simply seeing the last one in the stack. You should modify coms so that each item has it's own revele, e.g.:
coms = [
{
imageUrl: 'asdf',
revele: false
},
{
imageUrl: 'zxcv',
revele: false
},
{
imageUrl: 'ghjk',
revele: false
}
];
then inside your v-for:
<modale
:image-url="com.imageUrl"
:revele="com.revele"
#toggle-modale="com.revele = false"
></modale>
<img class="photo" :src=""" alt="image du commentaire" #click="com.revele = true">
passing the same function as a prop to each modal to control the value of revele is also a bad idea. Anytime a prop value needs to be modified in a child component, the child should emit an event telling the parent to modify the value. Notice in my code snippet above I replaced the prop with an event handler that turns the revele value specific to that modal to false. Inside each modal you should fire that event:
modale.vue
<div class="btn-modale btn btn-danger" #click="$emit('toggle-modale')">
X
</div>
This way you don't need any function at all to control the display of the modals.
import BlogData from "./BlogData";
import Link from 'next/link'
function Card(props) {
return (
<>
<div className="post-content">
<div className="post-image">
<Link href={props.to}><a><img srcSet={props.srcset} src={props.src} alt={props.alt} width={props.width} height={props.height} /></a></Link>
</div>
<div className="post-data">
<Link href={props.to}><a><h2>{props.heading}</h2></a></Link>
<p>{props.para}</p>
<Link href={props.to}><a><button className="read-more">Read More <i className="fas fa-arrow-right"></i>
</button></a></Link>
</div>
</div>
<hr />
</>
)
}
const Blog = () => {
return (
<>
<main>
<div className="blogpage">
<section className="posts">
<h1>Blog Posts</h1>
{BlogData.map((val) => {
return (
<Card key={val.id}
srcset={val.srcset}
src={val.src}
alt={val.alt}
width={val.width}
height={val.height}
href={val.to}
heading={val.heading}
para={val.para} />
);
})
}
</section>
</div>
</main>
</>
)
}
export default Blog;
There is an error that Failed prop type: The prop href expects a string or object in <Link>, but got undefined instead. I am passing href link using props but getting this error. The card component is used to display the data. I am fetching data from BlogData array where data is stored as an object. Can somebody tell me how can I fix this issue?
You pass in the wrong prop. As far as I can see you pass href prop, but expect to
<Card
key={val.id}
srcset={val.srcset}
src={val.src}
alt={val.alt}
width={val.width}
height={val.height}
to={val.to} // need to change here
heading={val.heading}
para={val.para}
/>
I have a profile page that displays the user info. The page shows the user name / email and a button to create a list.
I can also edit the name and email correctly, and it reflects in the firebase instantaneously. Ok. I get the user data and I can edit it.
What I'm trying to do now is to show the lists that the user has created.
Look, this user has created one list, and what is returned to me is that he doesn't have lists.
I'll try to shorten the code as much as possible:
<script>
imports.....
import { db } from '../../firebase.config.js'
let listings = []
let auth = getAuth()
// fetch the user's listings
const fetchUserListings = async () => {
const listingsRef = collection(db, 'listings')
const q = query(
listingsRef,
where('userRef', '==', auth.currentUser.uid),
orderBy('timestamp', 'desc')
)
const querySnap = await getDocs(q)
querySnap.forEach((doc) => {
return listings.push({
id: doc.id,
data: doc.data()
})
})
}
fetchUserListings()
</script>
<!-- display the user's listings -->
<div>
{#if listings.length > 0}
<p class="listingText">My lists</p>
{#each listings as listing}
<ListingItem listing={listing.data} id={listing.id} />
{/each}
{:else}
<p class="noListings">You have no lists</p>
{/if}
</div>
My ListItem component:
<script>
export let listing
export let id
export let handleDelete
import DeleteIcon from '../../static/assets/svg/deleteIcon.svg'
</script>
<li class="categoryListing">
<a href={`/category/${listing.type}/${id}`} class="categoryListingLink">
<img src={listing.imgUrls[0]} alt={listing.name} class="categoryListingImg" />
<div class="categoryListingDetails">
<p class="categoryListingLocation">
{listing.location}
</p>
<p class="CategoryListingName">
{listing.name}
</p>
<p class="categoryListingPrice">
${listing.offer ? listing.discountedPrice : listing.regularPrice}
{listing.type === 'rent' ? '/ por mês' : ''}
</p>
<div class="categoryListingInfoDiv">
<img src="/assets/svg/bedIcon.svg" alt="cama" />
<p class="categoryListingInfoText">
{listing.bedrooms > 1 ? `${listing.bedrooms} camas` : `${listing.bedrooms} cama`}
</p>
<img src="/assets/svg/bathtubIcon.svg" alt="banheiro" />
<p class="categoryListingInfoText">
{listing.bathrooms > 1
? `${listing.bathrooms} banheiros`
: `${listing.bathrooms} banheiro`}
</p>
</div>
</div>
</a>
{#if handleDelete}
<DeleteIcon
class="removeIcon"
fill="rgb(231, 76, 60)"
onClick={() => {
handleDelete(listing.id, listing.name)
}}
/>
{/if}
</li>
Just when you think you've reached the simplest part, it's still tough.
Update:
I think that the problem is in firebase. The "docs" are empty:
Now I am in serious trouble!
querySnap.forEach((doc) => {
return listings.push({
id: doc.id,
data: doc.data()
})
})
I see two things here. The less important: The .forEach() method returns undefined, so the return is redundant. The more important: the .push() alone won't automatically trigger updates. Have a look at this section in the Docs
Did you try logging listings? I assume the data is there, it's just not displayed, so I propose to change this part to
querySnap.forEach((doc) => {
listings = [...listings, {
id: doc.id,
data: doc.data()
}]
})
or
querySnap.forEach((doc) => {
listings.push({
id: doc.id,
data: doc.data()
})
listings = listings
})