I have the following structure:
- blog (directory)
-- index.js (list of all blog articles)
-- [slug].js (single article)
when I am inside index.js I have:
const Blog = props => {
const { pageProps: { articles } } = props
const blogArticles = objectToArray(articles)
return (
<Layout bio={props.bio}>
<Title>
I am excited by the idea of sharing my knowledge and perhaps inspiring others.
</Title>
{blogArticles.map(
(article, i) => <Card key={`id-${i}`} data={article} />
)}
</Layout>
)
}
Blog.getInitialProps = async () => {
const articles = await getBlogArticles()
return { articles }
}
and the card component has the link:
...
<Link href={`/blog/${slug}`}>
<Wrapper>
<ImgWrapper>
<Img src={BASE_URL + url} />
</ImgWrapper>
<TextWrapper>
<Title>{title}</Title>
<ArtcilePreview>{intro}</ArtcilePreview>
</TextWrapper>
</Wrapper>
</Link>
...
and inside [slug].js I have:
const BlogArticle = (props) => {
return (
<Layout bio={props.bio}>
<Article title={props.pageProps.article[0].title} content={props.pageProps.article[0].content} />
<ArticleShare url={'process.env.API_URL + asPath'} />
</Layout>
)
}
BlogArticle.getInitialProps = async ({ query }) => {
const article = await getArticleBySlug(query.slug)
return { article }
}
when I click within the card component to go to the dynamically generated page, it works correctly. However, during the transition from url/blog to url/blog/my-slug I can see an error message appearing and disappearing quickly in the console.
It looks like it cannot find the page that is dynamically generated throwing what I think to be a 500 error.
I cannot figure out why it appears and disappears so quickly.
ERROR BELOW I HAVE RECORDED A SCREEN VIDEO
According to the nextjs documentation you should not add the actual slug in href
If you check the docs of Link I linked above correct usage is
<Link href="/blog/[slug]" as={`/blog/${slug}`}>
Related
In my Next js app I'm Passing an Object through pages. what I did is compress my array of objects into JSON JSON.stringify(result) from index page and in my second page I parsed it JSON.parse(props.router.query.result). this worked great. but the issue is when reloading the page the browser prompts
This page isn’t workingIf the problem continues, contact the site owner.
HTTP ERROR 431
I know this message indicates for long url head. so is there a way for me to shorten this?
index page
<Link href=
{{pathname: "/tv-shows",
query: {
result: JSON.stringify(result),
//result: [result],
img: img,
index: index,
}}} key={index}
>
<div className=' relative snap-center h-56 w-96 rounded-3xl hover:rounded-3xl hover:scale-110 hover:transition-all hover:duration-200 hover:ease-in ease-out duration-200 '>
<Image
src={img}
layout="fill"
objectFit="cover"
className={`h-full w-full bg-cover bg-no-repeat rounded-3xl hover:rounded-3xl hover:scale-110 hover:transition-all hover:duration-200 hover:ease-in ease-out duration-200`} />
</div></Link>
in second page
const TvShows = (props) => {
const [result, setResult] = useState([]);
const [index, setIndex] = useState("");
const [img, setImg] = useState("");
useEffect(()=>{
console.log("first")
console.log(props.router.query);
if (props.router.query.result){
const query = props.router.query;
const res = JSON.parse(query.result);
setResult(res);
setIndex(query.index);
setImg(query.img);
//console.log(JSON.parse(props.router.query.result));
}
},[props.router.query ])
return (
<div className=''>
<Head>
<title>{Popular[Number(index)].title} | </title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Background result={result} img={img} index={index} />
{/* <Background img={img} index={index} /> */}
<div className='cursor-default px-10'>
<div className=' text-xl pt-5 pb-5'>Full Episodes </div>
{/* <Contents result={result} index={index}/> */}
</div>
</div>
)
}
export default withRouter(TvShows)
please help me with the fix
Based on comments to your original post, I deducted that you do not want to shorten a very long URL, but you are trying to pass data between subpages of Next app and save it so it is accessible after page refresh. What you can do to solve your issue is saving your result to localStorage every time it changes:
useEffect(() => {
localStorage.setItem("result", JSON.stringify(result))
}, [result])
And then, in your second page read the data:
useEffect(()=>{
const result = JSON.parse(localStorage.getItem("result"))
console.log("first")
console.log(result);
if (result){
setResult(result);
setIndex(query.index);
setImg(query.img);
}
}, [])
After comments to this Answer:
I think that what you want to do is creating a page tv-shows, which will display the details of one Youtube playlist. Best way to get this working is by creating dynamic routes.
Create the following directory structure in your app:
root
└── pages
└── tv-shows
└── [index].js
Paste this into the file [index].js
import { useRouter } from "next/router";
export async function getStaticPaths() {
return {
paths: [{ params: { index: "0" } }, { params: { index: "1" } }],
fallback: false
};
}
export async function getStaticProps(context) {
const MY_PLAYLIST_1 = "PL9562CjMJkXIgaV_UA5hf1VADfn4Sqd0P";
const MY_PLAYLIST_2 = "PL9562CjMJkXIgaV_UA5hf1VADfn4Sqd0P";
const API_KEY = "AIzaSyCELe0KoZYBjonJskBMbzdlTuCow3sr3zo";
const PLAYLIST_REQUEST_URL_1 = `https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=500&playlistId=${MY_PLAYLIST_1}&key=${API_KEY}`;
const PLAYLIST_REQUEST_URL_2 = `https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=500&playlistId=${MY_PLAYLIST_2}&key=${API_KEY}`;
const playlistResponse1 = await fetch(PLAYLIST_REQUEST_URL_1);
const playlistResponse2 = await fetch(PLAYLIST_REQUEST_URL_2);
const playlistResult1 = await playlistResponse1.json();
const playlistResult2 = await playlistResponse2.json();
return {
props: {
results: [playlistResult1, playlistResult2],
},
revalidate: 3600,
};
}
export default function TvShows({results}) {
const router = useRouter();
const { index } = router.query;
return <div>{index}, {JSON.stringify(results[index])}</div>;
}
Next step, override content of card.js with the following (just remove result variable and the query parameter)
import Link from "next/link";
const Card = ({ index }) => {
return (
<nav>
<Link
href={`/tv-shows/${index}`}
>
<div>
<h1>card click</h1>
</div>
</Link>
</nav>
);
};
export default Card;
Override index.js to remove unnecessary API calling code and take new card.js's props into account:
import Link from "next/link";
import Card from "../comps/card";
import Popular from "../comps/popular";
export default function IndexPage() {
return (
<div>
Hello World. {/* <Link href="/about">
<a>About</a>
</Link> */}
<Card index={0} img={Popular[0].img} />
<Card index={1} img={Popular[1].img} />
</div>
);
}
How the solution works is as follows:
We create dynamic routes which takes only query parameter index of our playlist. Every index parameter that is possible to be set is defined in paths: [{ params: { index: "0" } }, { params: { index: "1" } }]. These path params are then passed to our dynamic route which is then pre-rendered, and downloading all the data only once. And finally, our route displays data based on query parameters supplied by useRouter.
so i'm creating my first fullstack website and once a user signs in it gets stored in the localStorage and i want to display the name of the user in my header once he is logged in but my header is not re rendering so nothing happens : this is the header before logging in
header
and this is how i want it to Be after signing in :
header after logging in this is my Layout code:
import "../assets/sass/categoriesbar.scss";
import Header from "./Header/Header";
const Layout = (props) => {
return (
<>
<Header/>
<main>
{ props.children}
</main>
</>
);
}
export default Layout;
and this is the toolBar in my Header :
const ToolBar = () => {
const history = useHistory();
let currentUser= JSON.parse(localStorage.getItem("user-info"));
const logoutHandler = () => {
localStorage.clear("user-info");
history.push("/login");
};
return (
<>
<div className={classes.NavigationBar}>
<h1>
<Link to="/">Pharmashop</Link>
</h1>
<NavLinks logout={logoutHandler}/>
{localStorage.getItem("user-info")?
<h5>Welcome {currentUser.name} !</h5>
:
<RegisterButton />
}
</div>
</>
);
};
export default ToolBar;
please help me it's frustrating
PS: this is my first stackoverflow question sorry if it's unorganized and unclear and sorry for my bad english.
Hazem, welcome to Stack Overflow.
In react, if you want the component to re-render when some data changes, that info must be in the component state. In your code the current user is a const, not bind to the component's state. This is how it could auto re-render when the user logs in:
const ToolBar = () => {
const [currentUser, setCurrentUser] = useState(JSON.parse(localStorage.getItem("user-info")));
const logoutHandler = () => {
localStorage.clear("user-info");
history.push("/login");
};
return (
<>
<div className={classes.NavigationBar}>
<h1>
<Link to="/">Pharmashop</Link>
</h1>
<NavLinks logout={logoutHandler}/>
{currentUser?
<h5>Welcome {currentUser.name} !</h5>
:
<RegisterButton />
}
</div>
</>
);
};
export default ToolBar;
See more about state in the official documentation.
On my site, the <ArticleList> is supposed to update when one navigates between columns. This works when you go from the home page to a column, or from an article to a column. But if you go from column to column, it doesn't work. The page doesn't update at all, but the url changes. The links to each column stay the same, as they are part of the <Layout> component, which every page has.
Edit
I figured out now that I can just use <a> and omit <Link> entirely, but this would slow down the page navigation.
Edit 2
This is part of my <Layout> component where I render the links to the columns:
<nav className={layout.columnContainer}>
{columns.map(({ id, name }) =>
this.props.currentColumn ? (
<a key={id} href={`/columns/${name}`}>
{name}
</a>
) : (
<Link key={id} href="/columns/[name]" as={`/columns/${name}`}>
<a>{name}</a>
</Link>
),
)}
</nav>
Edit 3
My minimal reproducible example is on GitHub, and I get the same unexpected results.
Edit 4
I found that the reason it wasn't working was I implemented a search bar that put the children prop in a state and modified this.
Constructor:
constructor(props) {
super(props);
this.searchArticlesKeyType = this.searchArticlesKeyType.bind(this);
this.state = {displayedMain: props.children};
}
Inside the render method are the column links (nav) and the problematic search input element.
<nav className={layout.columnContainer}>
{
columns.map(({id, name}) => (
<Link key={id} href="/columns/[name]" as={`/columns/${name}`}><a>{name}</a></Link>
))
}
</nav>
<div className={layout.search}>
<input type="search" name="q" onKeyUp={this.searchArticlesKeyType} />
</div>
async searchArticlesKeyType(e) {
// Some code
this.setState({
displayedMain: <ArticleList articles={JSON.stringify(filteredArticles)}/>
// More code
});
}
I think your main issue here is the way you're implementing the search feature, you don't want to store components in state instead you need to pass the search text to the articlelist component and do the filtering there.
There are several ways to implement communication between 2 unrelated components, it could be via context, redux, or even make a portal in the layout to render the seach input from the column component, but in this case I think the best option is to store the search text in the url:
First make the input event update the url using next/router, your layout will look like this:
import { useRouter } from 'next/router'
...
function Layout(props) {
const {columns} = props;
const { push, asPath, query } = useRouter()
const searchArticlesKeyType = (e) => {
const q = e.target.value;
const [url] = asPath.split('?');
push(`${url}?q=${q}`, undefined, { shallow: true });
}
return (
<div>
...
<div>
<input type="search" name="q" defaultValue={query.q} onKeyUp={searchArticlesKeyType} />
</div>
...
</div>
)
}
And then you do the filtering in articlelist component
import Link from "next/link";
import { useRouter } from 'next/router'
export default function ArticleList(props) {
const { query } = useRouter();
const q = query.q || "";
const filteredArticles = props.articles.filter(
(item) => item.title.includes(q) || item.body.includes(q)
);
return (
<ul className="grid">
{filteredArticles.map((item) => (
<div key={item.id}>
<Link
key={item.id}
href="/articles/[title]"
as={`/articles/${item.title}`}
>
<a>
<p>
<strong>{item.title}</strong>
</p>
<p>{item.body.substring(0, 100)}</p>
</a>
</Link>
</div>
))}
</ul>
);
}
I am passing data from one page to another in gatsby. First page code:
let state = {book: book, src: src}
return (
<div className="book__holder">
<Link to="/pdf/" state={state}>
<Card
hoverable
style={{ width: 240 }}
cover={<img alt={book}
src={url}
/>}
>
<Meta
title={book}
description={faculty+" "+year+"-"+part}
/>
</Card>
</Link>
</div>
This data is used in pdf page as:
const PDFPage = props =>{
return (
<React.Fragment>
<SEO title={props.location.state.book} />
<NavBar></NavBar>
<Embed src={props.location.state.src} type="application/pdf">
</Embed>
</React.Fragment>
)}
export default PDFPage
Everything is fine when using gatsby develop but when i use gatsby build it throws following error:
error Building static HTML for pages failed
See our docs page on debugging HTML builds for help https://gatsby.app
/debug-html
11 | return (
12 | <React.Fragment>
> 13 | <SEO title={props.location.state.book} keywords={[`gatsby`,
`application`, `react`]} />
| ^
14 | <NavBar></NavBar>
15 | <Embed src={props.location.state.src} type="application/pdf">
</Embed>
16 |
WebpackError: TypeError: Cannot read property 'book' of undefined
- pdf.js:13 PDFPage
lib/src/pages/pdf.js:13:38
Can anyone help me please?
Gatsby will throw error during production build, since location is not available during server-side rendering.
One way to make sure the build doesn't throw an error is to:
Check for the window in componentDidMount
Map the location prop to state
Render the value from your state instead of directly from props
In componentDidMount()
componentDidMount() {
if (typeof window === 'undefined') {
return;
}
this.setState(() => ({ playerName: this.props.location.state.playerName }));
}
In render()
render() {
return <div>{this.state.playerName}</div>;
}
credit to this thread, How to get previous url in react gatsby
Sometimes you’ll want to pass data from the source page to the linked page. You can do this by passing a state prop to the Link component... The linked page will have a location prop containing a nested state object structure containing the passed data.
Passing Props to Link targets
While the following demo is not actually using gatsby, it is using reach router (and gatsby uses reach router under the hood).
import React from "react";
import { render } from "react-dom";
import { Router, Link } from "#reach/router";
const App = () => {
let state = {
name: 'Ron',
book: {
title: 'Harry Potter and the Deathly Hallows',
author: 'J. K. Rowling',
progress: '80%'
}
}
return (
<div>
<h1>App</h1>
<nav>
<Link to="/" state={state}>Home</Link>{" "}
<Link to="dashboard" state={state} >Dashboard</Link>
</nav>
<Router>
<Home path="/" />
<Dashboard path="/dashboard" />
</Router>
</div>
)};
const Home = ({location}) => (
<div>
<h2>Welcome { location.state.name }</h2>
<p></p>
</div>
);
const Dashboard = ({location}) => (
<div>
<h2>Dashboard</h2>
<p>Hi { location.state.name }.</p>
<p>You have read { location.state.book.progress } of { location.state.book.title } by { location.state.book.author }.</p>
</div>
);
render(<App />, document.getElementById("root"));
Stackblitz
I’m trying to access data from the Firebase server and then proceed to open a view page of the object. My routing is working for sure I think.
The index component I want the clicked post to link from is this:
renderPosts() {
return _.map(this.state.posts, (post, key) => {
return (
<div key={key} className="thumbnail">
<h2>
<Link to="/view/posts/{post}">
{post.title}
</Link>
</h2>
<p>{post.body}</p>
</div>
);
});
}
I tried to link to the post cause I figured it work based on the componentDidMount used to build that function. And I imported the view component into it as well.
The app.js page is as this:
<BrowserRouter>
<div>
<div>
<Nav/>
</div>
<Switch>
<Route path='/' exact component={List}/>
<Route path='/new' component={NewPost}/>
<Route path='/view' component={View}/>
</Switch>
</div>
</BrowserRouter>
</div>
);
}
}
And the view.js page is as follows:
componentWillMount(){
let postRef = database().ref('posts').orderByKey().limitToLast(100);
postRef.on('value', snapshot => {
let post = { title: snapshot.title, id: snapshot.key };
this.setState({ posts: [post].concat(this.state.posts) });
});
console.log(postRef);
}
}
render() {
return (
<div >
</div>
);
}
}
Sorry. It’s a bit bare now cause I removed everything I have tried. I used a couple articles and videos to try to figure it out but nothing worked. The original lifecycle method and constructor I used for the index page however is this:
constructor(props){
super(props);
this.state = {
title: '',
body: '',
posts:{}
};
this.onInputChange = this.onInputChange.bind(this);
this.onHandleSubmit = this.onHandleSubmit.bind(this);
}
and
componentDidMount(){
database.on('value', snapshot => {
this.setState({
posts: snapshot.val()
});
});
}
I’m sorry, I know it is a lot to get through but I thought I should cover everything that might be useful. But to sum up: My issues are how to retrieve the post from the database and then add the key that Firebase automatically makes to the link and thus render it in a new page.
Ps. My database is exported as this
export const database = firebase.database().ref('/posts’);
I don't understand the question but it sounds like the post key contains the value you want for the link. If that's the case then try...
renderPosts(){
return Object.keys(this.state.posts).map((post, index)=>(
<div key={index} className="thumbnail">
<h2>
<Link to={`/view/posts/${post}`}>
{this.state.posts[post].title}
</Link>
</h2>
<p>{this.state.posts[post].body}</p>
</div>
)}
);
then you should just be able to call your renderPosts function in your render method.
render() {
return (
<div >
{renderPosts()}
</div>
);
}
It might take a little while to get the post data if you're calling firebase so only call the render method when the post data shows up.
render() {
return (
<div >
{this.state.posts && renderPosts()}
</div>
);
}