I'm creating a website to showcase products using NextJs. I need to pass data from a page to a dynamic page. All the data is a json array of objects located in a data folder inside the project. I want to list all objects in pages/rolls/index.jsx, and when an objects corresponding "Learn More" Link button is clicked, takes me to a more detailed version of the page. I can't make it work using next/link and useRouter, so here's what I did.
My project structure is:
- data
- rollData.json
- pages
- rolls
- [rolldetail].jsx
- index.jsx
// rollData.json
[
{
"name": "8\" x 0.080\" x 300'",
"price": 300,
"product_id": "PL8080300",
"image": "/8flat.webp",
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
},
{
"name": "12\" x 0.080\" x 300'",
"price": 450,
"product_id": "PL12080300",
"image": "/12flat.webp",
"description": "Ac turpis egestas sed tempus urna et pharetra"
}
]
// rolls.jsx
import rollsdata from '../../data/rollData.json'
const Rolls = () => {
return (
<main>
{rolls.map((roll) => {
return (
<div key={roll.product_id}>
<h2>{roll.name}</h2>
<h3>
Description
</h3>
<p>{`${roll.description.slice(0, 163)}.`}</p>
<div>
<span>Price</span>
<span>{`$${roll.price.toFixed(
2
)}`}</span>
</div>
<Link href={`/rolls/${encodeURIComponent(roll.product_id)}`}>
<a>click</a>
</Link>
</div>
)
})}
</main>
)
}
// [rollDetail].js
import { useRouter } from 'next/router'
import rolls from '../../data/rolls.json'
const RollDetail = () => {
const router = useRouter()
const roll = rolls[router.query.rolldetail]
console.log(roll)
return (
<div>
<h2>Roll detail</h2>
<p>{roll.name}</p>
<p>{roll.description}</p>
</div>
)
}
export default RollDetail
And it gives me the error:
Uncaught TypeError: roll is undefined
I don't understand how to use router to display a specific roll object, can someone tell me what I'm doing wrong and how to achieve it please? Should I be using getStaticProps and getStaticRoute?
Actually by default all the routes either they are dynamic or static will be statically pre-rendered during build time of app, unless you use getStaticProps, getInitialProps or getServerSideProps...
But during development all the pages pre-render every time you hit the request to specific url, no matter what you have used...
Now lets back to actual question, your dynamic pages will be rendered twice first time you are getting undefined as your url parameters but on second render your url parameters will be availble, so you can simply put checks to check either url parameters are undefined or not...
This is happening due to some reason, checkout this link => click
Related
I have a problem when I try to get images from .json file in react, path is ok, also all other data are displayed with no problem, image take space and link can be seen in inspect but image itself is not shown. I have try literally everything I can do, images imported in classic way work fine (direct import not from .json file).
My code:
// import demo from "../../assets/icons/demo.jpg";
// import metalPan from "../../assets/icons/metalPan.jpg";
// import Foodie from "../../assets/icons/Foodie.jpg";
import "./Projects.scss";
import ProjectData from "../../json/projects.json";
function Projects() {
return (
<>
<h2 className="section_name">Projects</h2>
<div className="projects">
{/* Project card */}
{ProjectData?.map((info) => {
return (
<>
<div key={info?.id} className="project_wrraper">
<div className="project">
<h3 className="project_name">{info.name}</h3>
<div className="project_tech">
{info.stack.map((data) => {
return (
<div>
<span>{data.language}</span>
</div>
);
})}
</div>
<p className="project_descr">{info.description}</p>
<div className="project_stack">
<button className="stack-btn">Live version</button>
<button className="stack-btn">GitHub</button>
</div>
</div>
<img
className="project_img"
src={`${info.image}`}
alt=""
/>
</div>
</>
);
})}
{/* end of project card */}
</div>
</>
);
}
export default Projects;
[
{
"id": 1,
"name": "Metal Pan",
"stack": [
{"language": "JavaScript"},
{"language": "Sass"},
{"language": "BootStrap"},
{"language": "PHP"}
],
"image": "../assets/icons/metalPan.jpg",
"description": "Web Site made for company specialized in manufacturing and setting up industrial halls and metal roofs."
},
{
"id": 2,
"name": "Foodie",
"stack": [
{"language": "JavaScript"},
{"language": "Sass"},
{"language": "HTML5"}
],
"image": "../assets/icons/Foodie.jpg",
"description": "Personal project of responsive restaurant design with gallery, blog and menu!"
},
{
"id": 3,
"name": "eShop",
"stack": [
{"language": "React"},
{"language": "CSS"},
{"language": "HTML5"}
],
"image": "../assets/icons/eShop.jpg",
"description": "Personal project of eShop store using fake api, fetch all data, single product data and filter function."
}
]
I have tried to use many solutions but no luck some I will provide here (just to name few):
React, load images local from json
https://www.appsloveworld.com/reactjs/100/33/get-local-image-path-from-json-in-react
Are the images inside the public folder? The problem is very clearly that the image src is a relative path, but react will try to look them up in your public folder. If you replace the value of the images from "image": "../assets/icons/Foodie.jpg" to "image": "/assets/icons/Foodie.jpg" I think it should work.
You can solve this problem by following this approach-
Create a folder of any name (the recommended name would be assets or images) in the public directory.
So, the path to the image would be folderName/image.jpg.
So no matter what the absolute path is, it doesn't target the public folder.
Also if images are in the src folder you can't add them to the src tag. Just import it and that is why import work but the .json path doesn't.
Check out this SO solution, you may need to incorporate the require('../assets/...'); within your map function.
so it looks something like this.
<img className="project_img" src={require(${info.image})} alt=""/>
Im working on website for MMA gym. Gym has many fighters. Every fighter has his own card profile. For cards i use map function and information like name, nickname i have in array.
I'd like create page about fighter. So when someone click on the card. Page will show up with correct info. For example when i click on fighter1 page with info about fighter1 will show up.
I've made some popup logic but i have no idea how display correct information.
popup logic
const Fighter1= ({setIsOpenPopup}) => {
return (
<Container onClick={() => setIsOpenPopup(false)}>
<Content onClick={(e) => e.stopPropagation()}>
<Header>
<HeaderH3>Title here</HeaderH3>
<CloseBtn onClick={() => setIsOpenPopup(false)}>
</CloseBtn>
</Header>
<InfoP>
Lorem ipsum dolor sit amet consectetur, adipisicing elit.
Perferendis, molestiae.
</InfoP>
<FooterP>
XXX
</FooterP>
</Content>
</Container>
);
};
export default Fighter1;
at this time i have folder with pages fighter1, fighter2 etc. Yes i wanted use map function but i have no idea how then display proper info too.. This is easier way i thing.
For maping fighter cards i use this standart map function. It's just array and map
const DataAthletes = () => {
const athletes = [
{
id: 1,
firstName: 'David',
nickname: '"SPARŤAN"',
lastName: 'Hošek',
sport: 'MMA, BOX',
secondLineSport: '',
facebook: '/',
instagram: '/',
photo: Hosek,
button: 'Zjistit více'
},
{
id: 2,
firstName: 'Matůš',
nickname: '"SLÁVISTA"',
lastName: 'Juráček',
sport: 'MMA, BOX',
secondLineSport: '',
facebook: '/',
instagram: '/',
photo: Juracek,
button: 'Zjistit více'
},
]
return (
<AthleteContainer id="athletes">
<HeadingH2 size='3'>Naši Atleti</HeadingH2>
<AthleteMainP>Aktuálně nás na profesionální úrovní representují tito atleti. RPG též representuje mnoho atletů
na amatérské úrovni.
</AthleteMainP>
<CenterWrapper>
<AthleteWrapper>
{athletes.map(athlete =>
<Athletes athlete={athlete}/>
)}
</AthleteWrapper>
</CenterWrapper>
</AthleteContainer>
)
}
export default DataAthletes;
And finally in the end whole fighter card
const Athletes = ({athlete}) => {
const [imgHover, setImgHover] = useState(false);
const onHover = () => {
setImgHover(!imgHover)
}
const [isOpenPopup, setIsOpenPopup] = useState(false);
return (
<AthleteCards onClick={() => setIsOpenPopup(true)}>
{isOpenPopup &&
<Popup setIsOpenPopup={setIsOpenPopup} />}
<NameWrapperH3>
<FirstName>{athlete.firstName}</FirstName>
<Nickname>{athlete.nickname}</Nickname>
<LastName>{athlete.lastName}</LastName>
</NameWrapperH3>
<SportWrapper>
<SportName>{athlete.sport}</SportName>
<SportName>{athlete.secondLineSport}</SportName>
</SportWrapper>
<SocialWrapper>
<Link href={athlete.facebook}>
<FacebookIcon/>
</Link>
<Link href={athlete.instagram}>
<InstagramIcon/>
</Link>
</SocialWrapper>
<ImgWrapper>
<AthletePhoto src={athlete.photo}/>
</ImgWrapper>
</AthleteCards>
)
}
export default Athletes;
I can say popup works relatively fine. There are some issues but i think it's CSS issue so it's for another topic maybe.
I'd like to ask with big pleased request. How can i display proper fighter page depending on which card i click please?
I am building a website using vue with nuxt, loading data from a wordress site through the rest api.
I would like to give the client the ability to modify page templates using custom fields, so I need to dynamically create my vue templates with vue components that are generated depending on the custom fields placed in the wordpress page editor.
This is simplified, but for example, if the clients builds a page with three custom fields:
[custom-field type='hero']
[custom-field type='slider']
[custom-field type='testimonial']
I can get the field information via the rest api in a json object, like this:
page: {
acf: [
{field 1: {
{type: 'hero'},
{content: '...'}
},
{field 2: {
{type:'slider'},
{content: '...'}
},
{field 3: {
{type:'testimonial'},
{content: '...'}
}
}
}
I'll bring this into my vue app, but then i would the template to generate dynamically from a list of possible components mapped to the custom field types. the above would output:
<template>
<Hero ... />
<Slider ... />
<Testimonial ... />
</template>
Would this be done using the v-is directive (https://v2.vuejs.org/v2/guide/components-dynamic-async.html) like:
<component v-for="field in custom-fields" v-is="field.type" :data="field.data"/>?
Is this possible? Any help would be greatly appreciated.
You can dynamically register and display components in Nuxt but there are some caveats.
Method 1 - SSR & SSG Support
This method will register your components dynamically and maintain the integrity of Server Side Rendering/Static Site Generation.
The only trade-off here is that you will need to list the names and file locations of all potential imported components. This shouldn't be too cumbersome for your use-case (I don't imagine you have too many ACF fields) but it could be a lengthy job if you're intending to build an entire component library from it.
<template>
<div>
<div v-for="field in page.acf" :key="field.uniqueId">
<component :is="field.type" :my-prop="field.content" />
</div>
</div>
</template>
<script>
export default {
components: {
hero: () => import('~/components/hero.vue'),
slider: () => import('~/components/slider.vue'),
testimonial: () => import('~/components/testimonial.vue')
},
data() {
return {
page: {
acf: [
{
uniqueId: 1,
type: 'hero',
content: '...'
},
{
uniqueId: 2,
type: 'slider',
content: '...'
},
{
uniqueId: 3,
type: 'testimonial',
content: '...'
}
]
}
}
}
}
</script>
Method 2 - Client Side Only Rendering
This method allows you to programmatically register the component's name and file location. This saves you from writing out each component one by one, but comes at the cost of no SSR or SSG support. However, this may be preferable if you're going the SPA route.
<template>
<div>
<div v-for="field in page.acf" :key="field.uniqueId">
<no-ssr>
<component :is="field.type" :my-prop="field.content" />
</no-ssr>
</div>
</div>
</template>
<script>
import Vue from 'vue'
export default {
data() {
return {
page: {
acf: [
{
uniqueId: 1,
type: 'hero',
content: '...'
},
{
uniqueId: 2,
type: 'slider',
content: '...'
},
{
uniqueId: 3,
type: 'testimonial',
content: '...'
}
]
}
}
},
mounted() {
const sections = this.page.acf
for (let i = 0; i < sections.length; i++) {
Vue.component(sections[i].type, () =>
import(`~/components/${sections[i].type}.vue`)
)
}
}
}
</script>
Be aware that the <no-ssr> tag is being deprecated and if you are using Nuxt above v2.9.0, you should use <client-only> instead.
Notes On Your Question
I understand you've tried to simplify your data architecture but your JSON object would be rather difficult to loop through since the key changes on each object in array. You've also got unnecessary objects in the data structure. I've simplified the structure in the data method so you can understand the concept.
To get the v-for loop working, you need to nest the component inside a HTML tag that has a v-for attribute (as demonstrated above).
You may want to sanitise the data you get from the WordPress API by ensuring that WordPress doesn't supply you with a module that doesn't correspond to a component. If the API supplies a type that doesn't correspond to a component, the whole build will fail.
Hope this helps!
P.S If anyone knows of a method that allows you to programatically set the component name and component file location while maintaining SSG - please let me know!
I'm relatively new to react and javascript (I'm usually backend Java). I'm currently trying to make an image gallery, where the subsections are clicked on and thumbnails of the images within each section are displayed. The thumbnails can then be clicked on and open a lightbox (I'm utilizing simple-react-lightbox for this and am on the latest version).
It works, but inconsistently. Sometimes when I click on the thumbnail, instead of throwing up the lightbox and being able to click through the images, it takes me directly to the full sized image.
My nested array with the image data, (imports of images omitted)
const GALLERIES =[
{
name: 'Beaded Jewelry',
id: 'beadwork',
description: 'placeholder',
resources:[
{
id: 1,
thumbnail: bluependant,
url: blue,
title: 'Blue Swarovski Pendant'
},
{
id: 2,
thumbnail: greenpendant,
url:green,
title: 'Green Swarovski Pendant'
}
]
},
{
name: 'Wire Jewelry',
id: 'wirewrapped',
description: 'placeholder #2',
resources:[
{
id: 'wire1',
thumbnail: wire1Thumb,
url: wireWrap1,
title: 'placholder 1'
},
{
id: 'wire2',
thumbnail: wire2Thumb,
url:wireWrap2,
title: 'placholder 1'
}
]
}
];
export default GALLERIES
And the js that I'm trying to make work consistently.
import React from 'react';
import '../css/gallery.css';
import '../css/jewelry_main.css';
import GALLERIES from '../data/galleries';
import SimpleReactLightbox from "simple-react-lightbox";
import { SRLWrapper } from "simple-react-lightbox";
import {Link } from 'react-router-dom';
const Gallery = props =>{
const{thumbnail, url, title } = props.subgallery;
return(
<div className="gallery">
<a href={url} data-attribute="SRL">
<img src={thumbnail} alt={title} />
</a>
</div>
)
}
const JewelryGallery = ({match}) =>{
const gallery = GALLERIES.find(({ id }) => id === match.params.keyword)
return(
<div>
<div className="frosted">
<p>{gallery.description}</p>
</div>
<SimpleReactLightbox>
<SRLWrapper>
{
gallery.resources.map((SUBGALLERY) =>{
return(
<Gallery key = {SUBGALLERY.id} subgallery={SUBGALLERY} />
)
})
}
</SRLWrapper>
</SimpleReactLightbox>
</div>
);
}
export default JewelryGallery;
Any insight or wisdom would be greatly appreciated! Thank you!
UPDATE EDIT: I noticed something, the first subgallery I click into will always present with the lightbox, but the second one, consistently fails and instead goes directly to the fullsize of whichever image I have clicked on. (It doesn't matter which of the two I pick first, the second always fails.) But I can't discern why the lightbox would not get applied to the second gallery I select.
There are only two things I can see that differ from the documentation for the simple-react-lightbox package.
First thing is, with the latest version, you only need to use the <SRLWrapper> component. You have both the <SRLWrapper> and the <SimpleReactLightbox> component in use. Perhaps try getting rid of the old version if you have the latest version of the package installed.
Second thing is the structure of your Gallery component return has the div wrapping the elements. I'm not sure if this would cause any problems but, to get as close as possible to the example in the docs you could try using a fragment instead of a div.
const{thumbnail, url, title } = props.subgallery;
return(
<>
<a href={url} data-attribute="SRL">
<img src={thumbnail} alt={title} />
</a>
</>
)
I haven't used the package so this is a bit of speculation. Hope it leads you in the right direction though.
EDIT
I think I misunderstood this library a bit. You do need both of those components. It looks like if you want to display full size images, you would do it the way you have it currently. However, if you want to use the lightbox feature with the nav at the bottom you would need to do something like:
<SimpleReactLightbox>
<SRLWrapper>
<div id="content-page-one" className="container content">
<div className="row">
{
gallery.resources.map((SUBGALLERY) =>{
return(
<Gallery key = {SUBGALLERY.id} subgallery={SUBGALLERY} />
)
})
}
</div>
</div>
</SRLWrapper>
</SimpleReactLightbox>
And then you would use div instead of an anchor tag:
<div className="col-md-6 col-12 col-image-half">
<img src={thumbnail} alt={title} />
</div>
This is pulled from the Code Sandbox examples they have. I would take a closer look there and see if they have the exact setup you're looking for. Notice also that they are using the Bootstrap styling framework as well, so in order for those styles to work you would need to install bootstrap to your dependencies.
I've got this simple vue single file component
<template>
<v-layout>
<v-flex xs12 sm6 offset-sm3>
<v-card v-bind:color="color" class="white--text">
<v-card-title primary-title>
<div>
<h3 class="headline mb-0">Kangaroo Valley Safari</h3>
<div>{{card_text}}</div>
</div>
</v-card-title>
</v-card>
</v-flex>
</v-layout>
</template>
<script>
import MessageCard from '../components/MessageCard.vue';
const colors = [
'red',
'pink',
'purple',
'indigo',
'green'
];
export default {
data () {
return {
card_text: 'Lorem ipsum dolor sit amet, brute iriure accusata ne mea. Eos suavitate referrentur ad, te duo agam libris qualisque, utroque quaestio accommodare no qui.'
}
},
computed: {
color: function () {
const length = colors.length;
return colors[Math.round(-0.5 + Math.random() * (length + 1))] + ' darken-3';
}
},
components: {
MessageCard
}
}
</script>
The problem is that by server-side render I am getting computed color of v-card as a style, but when the client side hydration starts computed property recalculates which changes the style and causes rerender.
Of cause, I can fix it fix tag, but I'm curious is there some other ways to make it work correctly.
Computed properties are always reevaluated during client side hydration. It is generally not a good idea to relay on side effects in your computeds (like Math.random()), since Vue.js expects computed properties to be idempotent.
So usually you would calculate that random value once at creation and store it as data. However the data of a component is also not preserved between SSR and hydration.
So a way you could solve this, would be by storing the random value in the state of a Vuex store. It is then possible to restore the state of the store form the server in the client.