I'm trying to render a blog as a card then open it up as a page , but its proving to be difficult using Gatsby. I did the exact same thing fine with react using React router but it doesn't seem to be working with Gatsby. I know I can use GraphQL but surely I can do the same thing using REST. Im using Contentful btw
I switched to reach router as suggested in another post but that doesnt work.
I kept getting this error when I used react-router-dom:
Invariant failed: You should not use <Link> outside a <Router>
Fetching Blog contents
function Blog() {
const [blogs, setBlogs] = useState([])
const [image, setImage] = useState()
const [selectedBlog, setSelectedBlog] = useState(blogs)
useEffect(() => {
fetch("http://cdn.contentful.com...")
.then(response => response.json())
.then(data =>
setBlogs(data.items)
)
}, [])
console.log(blogs)
return (
<>
<div className="card-flex" >
{selectedBlog !== null ? blogs.map((blog =>
<Card title={blog.fields.title} date={blog.fields.date} introduction={blog.fields.introduction} mainBody1={blog.fields.mainBody1} mainBody2={blog.fields.mainBody2} setSelectedBlog={selectedBlog}
/>
)):
<Article title={blogs.find(d => d.fields.title === selectedBlog)} />
}
</div>
</>
)
}
export default Blog
Blog Card
function Card(props) {
console.log(props)
return (
<div class="container">
<div class="card">
<div class="card-header">
<img style={{backgroundImage: "url('https://i.pinimg.com/564x/7f/bb/97/7fbb9793b574c32f5d28cae0ea5c557f.jpg')"}}/>
</div>
<div class="card-body">
<span class="tag tag-teal">{props.tags}</span>
<h4>{props.title}</h4>
<p style={{fontSize:"17px", paddingTop:"10px"}} >{props.introduction}</p>
<div class="card-user">
<Link
to={{
pathname: '/article',
state: {
title: props.title,
introduction: props.introduction
}
}}
>
<button>read more</button>
</Link>
<div class="user-info">
<h5 >{ props.date}</h5>
</div>
</div>
</div>
</div>
</div>
)
}
export default Card
**Article **
import React from 'react'
import './Article.css'
import { useLocation } from "#reach/router"
function Article(props) {
// useLocation to access the route state from Blog.js
const { state = {} } = useLocation();
console.log(state)
return (
<div className="main">
<h1 className="title">{state.title}</h1>
<p className="intro">{state.introduction}</p>
<p className="main1">{state.mainBody1}</p>
<p className="main2">{state.mainBody2}</p>
</div>
)
}
export default Article
I think you are mixing stuff. Gatsby extends from #reach/router so you don't need to use its notation. Your Link should look like:
<Link
to={`/article`}
state={{
title: props.title,
introduction: props.introduction
}}
>
Assuming your /article page exists under /pages folder.
Related
I am working on a react project and trying to show more details on a product with react-router useParams, but I keep getting the error 'Cannot destructure property 'title' of 'product' as it is undefined.
Here's the code for the single products
import React from "react";
import products from "../data2";
import { Link, useParams } from "react-router-dom";
const SingleProducts = () => {
const { productId } = useParams();
const product = products.find((productg) => productg.id === productId);
const { title } = product;
return (
<section className="section product">
<img />
<h3>{title}</h3>
<h3></h3>
<Link to="/products">Back home</Link>
</section>
);
};
export default SingleProducts;
and here's for the product items
import React from 'react'
import { products } from '../data2'
import style from '../Styles/ProductItem.module.scss'
import StarRateIcon from '#mui/icons-material/StarRate';
import {Link} from "react-router-dom"
const ProductItem = () => {
return (
<div className={style.container}>
{products.map((item)=>{
return (
<article key={item.id} className={style.product}>
<h1>{item.title}</h1>
<h1>{item.id}</h1>
<div>
<img src={item.images} />
</div>
<div>
<p>{item.title}</p>
<h4>${item.price}</h4>
<p>{item.stock} items left</p>
<div className={style.rating}>
<p>{item.rating}</p>
<StarRateIcon className={style.star}/>
</div>
</div>
<Link to={`/products/${item.id}`}><button className={style.btn}>View more</button></Link>
<div className={style.discount}>
<p>- {item.discountPercentage} %</p>
</div>
</article>);
})}
</div>
)
}
export default ProductItem
Array.prototype.find potentially returns undefined if no match is found. The component logic should handle this case and only attempt to access into defined objects. Add a check on the returned product value and conditionally render alternative UI.
You should also be aware that the route path params will always be a string type, so you'll want to ensure you are using a type-safe comparison in the find predicate function. Converting the value you are comparing against the path param to a string is sufficient.
Example:
const SingleProducts = () => {
const { productId } = useParams();
const product = products.find((productg) => String(productg.id) === productId);
if (!product) {
return "No matching product found.";
}
const { title } = product;
return (
<section className="section product">
<img />
<h3>{title}</h3>
<h3></h3>
<Link to="/products">Back home</Link>
</section>
);
};
I am currently experiencing issues when trying to pass data from my Slider (SwiperJS as parent component) in to my dynamic Card component as a child.
I am using SWR to data fetch from an api endpoint (dummy data) in order to test functionality.
Here is my code...
./components/Slider.js - Parent component
import React, { useState } from 'react'
import { Swiper, SwiperSlide} from 'swiper/react'
import { Virtual, Navigation, FreeMode } from "swiper"
import { SongCard } from './Songcard'
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((res) => res.json())
// Import Swiper styles
import 'swiper/css';
import 'swiper/css/virtual'
import 'swiper/css/navigation'
import 'swiper/css/free-mode'
export default function Slider() {
const { data, error } = useSWR('https://api.npoint.io/fdb9617039cbd942c147', fetcher)
if(!data) return <div className={style.loading}>Loading</div>
if(error) return <div>Error</div>
// Create array with dynamic slides
const slides = Array.from({ length: data.length }).map(
(el, index) => `Slide ${index + 1}`)
console.log(data.length)
return (
<Swiper
modules={[Virtual, Navigation, FreeMode]}
slidesPerView={7}
virtual
centeredSlides={true}
initialSlide="3"
navigation={true}
grabCursor={true}
breakpoints={{
280: {
slidesPerView: 1,
},
360: {
slidesPerView: 2,
},
584: {
slidesPerView: 3,
},
768: {
slidesPerView: 4,
},
1024: {
slidesPerView: 5,
},
1260: {
slidesPerView: 6,
},
}}
>
{slides.map((slidecontent, index) => (
<SwiperSlide key={slidecontent} virtualIndex={index}>
{data.map((data) =>{
return <SongCard key={data.songId} />
})}
</SwiperSlide>
))}
</Swiper>
)
}
./components/SongCard.js - Child component
import Image from 'next/image'
import styles from '../styles/Card.module.css'
import Script from 'next/script'
export function SongCard(data) {
return (
<section>
<Script src="https://kit.fontawesome.com/4dcae86ee9.js" crossorigin="anonymous"/>
<div className={styles.container}>
<div className={styles.songImg}>
<div className={styles.imgWrapper}>
<Image
className={styles.songImage}
priority="true"
src="/counting.png"
alt={data.songName}
width="200px"
height="200px"
layout="intrinsic"
objectPosition="center"
/>
</div>
<div className={styles.songImage}>
<span><i className="fa-solid fa-fire-flame-curved fa-beat"></i></span>
</div>
<div className={styles.songInfo}>
<p className={styles.songTitle}></p>
<p className={styles.artistTitle}>{data.screenName}</p>
<p className={styles.vibes}>{data.songVibeTags}</p>
<p className={styles.genres}>{data.songGenreTags}</p>
</div>
<div className={styles.iconContainer}>
<span className={styles.likes}>
<i className="fa-solid fa-thumbs-up"></i>
<p className={styles.countLikes}>{data.songLikes}</p>
<i className="fa-solid fa-play"></i>
<p className={styles.countPlays}>{data.songPlays}</p>
</span>
<span className={styles.actions}>
<i className="fa-regular fa-bookmark"></i>
<i className="fa-solid fa-share-nodes"></i>
</span>
</div>
</div>
</div>
</section>
)
}
There seems to be two issues that I have here. The first is that the data is not passing in to the 'Child component'.
The second issue seems more troubling and that is that the number of slides seems to be greater than the number of objects that are in the data api endpoint.
It should be a Dynamic number of slides matching data.length.
As far as I can tell, I am only pulling back the initial data once to populate the slides.
This is causing all of the slides to scrunch up to try and occupy the same amount of space that the data length would normally have used.
Any and all advice would be much appreciated after Doc surfing for a few days now.
It is possible that I am trying to tackle the problem the wrong way...if so please guide me so I can refactor.
Thanks and have a great day.
First of all you forgot to pass the data to the SongCard component, do it like that:
// Add data prop here
<SongCard key={data.songId} data={data} />
// And don't forget to destructure it in the component:
export function SongCard({ data }) {
// ...
}
And second, do you even need this slides array? Just iterate over your data directly:
{data.map((song) => (
<SwiperSlide key={song.songId}>
<SongCard data={song} />
</SwiperSlide>
))}
I've made quick minimal reproduction on Codesandbox if you want to check working example:
I tried implementing browser router, but to no success. i'm having trouble with useParams hook, and just the router in general. Looked through multiple posts and i just wasn't able to get it working. I'll post the most barebones code below, hoping someone knows the solution. I removed the traces of the router, since it didn't work.
App.js is currently empty:
const App=()=> {
return (
<Main/>
);
}
Main.jsx is my main element, where components change. There isn't a page change per se, everything is in the main element. values get passed through props into main and written into state, so the useEffect can change visibility of components based on what you chose, first category, then recipe.:
const Main =()=> {
const [showElement, setShowElement] = useState("category");
const [selectedCategory, setSelectedCategory] = useState();
const [selectedRecipe, setSelectedRecipe] = useState();
useEffect(()=> {
if (selectedRecipe) {
setShowElement("recipe")
} else if (selectedCategory) {
setShowElement("recipeSelection")
}
window.scrollTo(0, 0)
}, [selectedCategory][selectedRecipe]);
return (
<>
<Header />
<main className="main">
<div>
<div>
{showElement === "category" &&
<CategoryWindow
passSelectedCategory={setSelectedCategory}
/>
}
</div>
<div>
{showElement === "recipeSelection" &&
<RecipeSelection
value={selectedCategory}
passSelectedRecipe={setSelectedRecipe}
/>
}
</div>
<div>
{showElement === "recipe" &&
<RecipeWindow
value={selectedRecipe}
/>
}
</div>
</div>
</main>
</>
)
}
This is the recipe picker component. For example when i click on curry, i'd like the url to show /food/curry. None od the names are hardcoded, everything comes from a javascript object:
const RecipeSelection =(props)=> {
const recipies = Recipies.filter(x=>x.type === props.value);
return (
<div className="selection-div">
<div className="selection-inner">
{recipies.map(selection =>
<>
<img src={require(`../images/${selection.id}.png`)}
className="selection-single"
key={selection.id}
alt={"picture of " + selection.id}
onClick={()=> props.passSelectedRecipe(selection.id)}
>
</img>
<div className="container-h3"
onClick={()=> props.passSelectedRecipe(selection.id)}
>
<h3 className="selection-h3">{selection.name}</h3>
</div>
</>
)}
</div>
</div>
)
}
In my nextjs project I'm displaying posts from different tags and each post have many tags. I have a post_by_tags component and I'm using that component in different sections on home page to display posts from different tags. I don't want to show repeating content as some posts have same tags and I have a array to keep post ids that are visible to website. Now I want a way to keep post ids from child component to send back to parent component which updates the array so I can filter out these posts from post object. I find some examples but mostly these are tied with onclick or onchange something like that.
Here is my parent component code:
import Head from 'next/head'
import Image from 'next/image'
import Layout from '../components/Layout';
import Hero from '../components/Hero';
import Developed_country from '../components/Developed_country';
import Posts_by_tags from '../components/Post_by_tags';
import Attorneys from '../components/Attorneys';
import Business_formation from '../components/Business_formation';
import Case from '../components/Case';
export async function getServerSideProps(context) {
// Fetch data from external API
const res = await fetch(`https://dashboard.toppstation.com/api/blogs`);
const data = await res.json();
// Pass data to the page via props
return {
props: { blogs:data}
}
}
export default function Home({blogs}) {
const blog_post_id = [];
const pull_data = (data) => {
console.log(data); // LOGS DATA FROM CHILD)
}
return (
<Layout>
<Hero/>
<Developed_country/>
<Posts_by_tags tag='business' bg='bg_grey' posts={blogs} func={pull_data}/>
<Business_formation/>
<Attorneys/>
<Posts_by_tags tag='png' bg='bg_white' posts={blogs} />
<Posts_by_tags tag='image' bg='bg_grey' posts={blogs} />
<Posts_by_tags tag='png' bg='bg_white' posts={blogs} />
<Case/>
</Layout>
)
}
Child component:
import Blogimg from '../public/img/blog.png';
import btn_arrow from '../public/img/btn_arrow.svg';
import styles from '../styles/Home.module.css';
export default function Posts_by_tags(props){
props.func('My name is xyz');
const bg = props.bg;
let align = ['start','center','end'];
let post_tags = [];
const postIds= [];
const blog_posts = props.posts.filter(bpost=>{
bpost.tags.forEach(tag => {
if(tag.toLowerCase() === props.tag){
return postIds.push(bpost._id);
}
})
});
const posts = props.posts.filter(p => {
if( (postIds.indexOf(p._id) !== -1) && p.visibility==true){
return p;
}
}).slice(0,3);
return(
<>
{posts.length == 0 ? null :(
<section id={styles.postsbytags} className={bg}>
<div className='wrapper'>
<div className="container section posts_by_tags section_ptb">
<div className='row'>
<div className='col-sm-12'>
<h3 className={`${styles.heading3} text-center`}><span className={`${styles.heading3span} ${bg}`}>{props.tag}</span></h3>
</div>
</div>
<div className='row pt_100'>
{posts.map( (post, index) =>(
<div id={`post-${post._id}`} className={`col-md-4 d-flex justify-content-md-${align[index]} justify-content-center`} key={post._id}>
<div className={styles.blog_post}>
<div className={`${styles.blog_image} text-center`}>
<span className={styles.blog_tag}>{props.tag}</span>
<Image className="img-fluid" src={post.image} alt={post.title} width={450} height={400} layout='responsive'/>
</div>
<div className='blog_content'>
<h4 className={styles.blog_title}>{post.title}</h4>
<p className={styles.blog_desc}>{post.description.split(' ').slice(0, 10).join(' ')}...</p>
</div>
</div>
</div>
))}
</div>
<div className='row'>
<div className='col-sm-12'>
<div className='blog_category pt_50'>
<a href="" className={ `btn ${styles.btn_tags} `}>See More {props.tag} <i className={styles.btn_icon}><Image src={btn_arrow} alt="btn-icon"/></i></a>
</div>
</div>
</div>
</div>
</div>
</section>
)}
</>
);
}```
This question already has an answer here:
How to add new pages without rebuilding an app with +150k static pages?
(1 answer)
Closed 11 months ago.
I've jus started working and learning Next so I have a lot of confusions,
I was using the useEffect on react and it always updated the UI with the new stuff that was added to the API however, its not working on next.js
SO I have an index file
import Link from "next/link";
import react, {useState, useEffect} from "react";
import { useRouter } from 'next/router';
export async function getStaticProps({ res }) {
try {
const result = await fetch(`https://api.pandascore.co/matches/running??sort=&page=1&per_page=10&&token=#`);
const data = await result.json();
return {
props: { game: data },
revalidate: 10 // 10 seconds
};
} catch (error) {
res.statusCode = 404;
return { props: {} };
}
}
const upcomingGames = ({ game }) => {
return (
<div className="container">
<h2>Live Games - </h2>
<div className="columns is-multiline">
{game.map(q => (
<div className="column is-half" key={q.id}>
<div className="inner">
<div className="inner__box">
<Link href = {'/live/' + q.slug} key={q.slug}>
<a className="h2link" key={q.slug}> {q.name}</a>
</Link></div>
</div>
</div>
))}
</div>
</div>
);
}
export default upcomingGames;
This file is connected to a [slug].js file which displays more details about a game,
Now in production when I deployed the app to vercel I have noticed that when a new game is added to the API it displays in the index.js but when I click on it I'm redirected to a fallback(404) page.
After I redeploy my project this is fixed, however every time a new game is added and rendered I'm unable to access its individual page which I defined in [slug].js
export const getStaticPaths = async () => {
const res = await fetch(`https://api.pandascore.co/matches/running?sort=&page=1&per_page=50&token=#`);
const data = await res.json();
const paths = data.map(o => {
return {
params: { slug: o.slug.toString() }
}
})
return {
paths,
fallback: false
}
}
export const getStaticProps = async (context) => {
const slug = context.params.slug;
const res = await fetch(`https://api.pandascore.co/matches/running?search[slug]=${slug}&token=#`);
const data = await res.json();
console.log(data)
return {
props: {
game: data
}
}
}
export default function live({ game }) {
return (
<div className="container">
<h2> Single Game deets.</h2>
{game.map((g) => (
<div className="container" key={g.id} >
<div className="inner-box" key={g.slug}>
{/** Fetch team and display their corresponding score - A bit of code repition :( */}
<div className="score-board-min columns is-mobile is-multiline">
<div className="column is-full"> {g.opponents.slice(0, -1).map((o) => <span className="team" key={o.id}>{o.opponent.name}</span>)}
{g.results.slice(0, -1).map((res, i) => (
<span className="scores" key={i}>{res.score}</span>
))}</div>
<div className="column">
{g.opponents.slice(-1).map((o) => <span className="team" key={o.id}>{o.opponent.name}</span>)}
{g.results.slice(-1).map((res, i) => (
<span className="scores" key={i}><div>{res.score}</div></span>
))}
</div>
</div>
<br />
<div className="lower-box columns is-multine">
<div className="column is-half">
<div className="dark"><span className="is-pulled-left">League</span> <span className="is-pulled-right">{g.league && g.league.name}</span></div>
<div className="dark"><span className="is-pulled-left">Game:</span> <span className="is-pulled-right"> {g.videogame && g.videogame.name} </span></div>
<div className="dark alt"><span className="is-pulled-left">Tournament</span> <span className="is-pulled-right"> {g.tournament && g.tournament.name} | </span></div>
<div className="dark"><span className="is-pulled-left">Series</span> <span className="is-pulled-right"> {g.serie.full_name} | {g.serie.tier.toUpperCase()} </span></div>
<div className="dark alt"><span className="is-pulled-left">Teams</span> <span className="is-pulled-right"> {g.opponents.map((o) => o.opponent.name).join(" vs ")} </span></div>
</div>
</div>
<br />
</div>
</div>
))}
</div>
)
}
During development (next dev) getStaticPaths gets called on every request, but for production it only gets called the next time you run next build. So when a new game is added to the API, the paths named after ${some_new_game_slug} won't exist until you run next build again, i.e., re-deploy. If this type of data changes frequently, you might have to use getServerSideProps for [slug].js as well (so no static paths) or opt for the client-side data fetching approach.