Fetch data at component level and have it prerendered/ssr nextjs - javascript

I'm working with Nextjs for the first time.
I'm trying to create multiple layouts which will consist on a <Header><different-layouts-for-each><Footer> structure.
The issue that I'm facing is that getStaticProps or getServerProps can run at the page level only.
Since I need SEO on the navigation I suppose I should get it's props on every single page file using one of the two mentioned methods.
The problem here is that I'd have to get the menu props on each one of the pages, but having different templates I will have to repeat myself on all of them in order to bring the content statically or prerendered and be SEO readable.
Getting the menu props on the <MainNav> component would be the ideal situation.
I tried doing an asnyc/await on the component:
<Header> component
import Logo from "../components/header/logo";
import MainNav from "../components/header/mainnav.js";
function Header() {
return (
<div className="headwrapper container mx-auto py-8 flex items-center">
<Logo />
<MainNav/>
</div>
);
}
export default Header;
<MainNav> component
import Link from "next/link";
import { WpHeader } from "../../lib/wpapi";
import React, { useEffect, useState } from "react";
function MainNav() {
const [nav, setNav] = useState(0);
useEffect(() => {
const fetchData = async () => {
const wp = new WpHeader();
const call = await wp.getAxiosMenu();
console.log(call);
setNav(call);
};
fetchData();
}, []);
return (
<div className="navigation text-right w-3/4">
<ul className="main-navigation">
{nav
? nav.map(item => (
<li key={item.id} className="inline-block mx-2">
<Link href={item.path}>
<a>{item.label}</a>
</Link>
</li>
))
: "loading"}
</ul>
</div>
);
}
export default MainNav;
The issue here is that this will return a promise and the html will show "loading" instead of the actual menu html.
Any help or article that could help me on this?
Thanks.

Related

React api router can't show data in detail

Im learning React with routing and api calls at the moment. I've got an api were I need to find all the urls and post them on a page that was successful. But now I need to make a detail page where I can show more information about the url. Page 1 is like : gather all the urls and show them in a list. And page 2 is if you have clicked on a url in the list on page 1 you will be navigated to page 2 where a detail dialog is showed.
I have the page 1 completely and I can navigate to page 2 but I can't show any data from the api in page 2 information/detailpage.I don't know how to work with the key and get the info. I Tried several things but none of them works. Im using link.Link as key and I need description property , url , api name.
This is my page 1:
import React from "react";
import {Link, NavLink} from 'react-router-dom';
import { Detail } from './Detail';
export default class FetchLinks extends React.Component {
state = {
loading: true,
links: [],
};
async componentDidMount() {
const url = "https://api.publicapis.org/entries?category=development";
const response = await fetch(url);
const data = await response.json();
this.setState({links: data.entries, loading: false});
}
render() {
if (this.state.loading) {
return <div>loading...</div>
}
if (!this.state.links.length) {
return <div>geen links gevonden</div>
}
return (
<div>
{this.state.links.map(link => (
<div key={link.Link}>
<ul><li>
<p>
<NavLink to={`/Detail/${link.Link}`}>
{link.Link}
</NavLink>
</p>
</li></ul>
</div>))}
</div>
);
}
}
this is page 2:
import React from 'react';
import Content from './Content';
const Detail = () =>{
return(
<div>
<h1>Detail Page</h1>
<p>{this.state.links.Link}</p>
</div>
)
}
export default Detail();
This is how it should be
So if I understood correctly, you have all your data in page 1 and need to pass the data to page 2. For this you have to write your NavLink like this:
<NavLink to={{
pathname: `/Detail/${link.Link}`,
state: link.Link
}}>
{link.Link}
</NavLink>
Now you can use UseLocation hook of react-router to access this state that you just passed. So in page 2:
import React from 'react';
import { useLocation } from "react-router-dom";
const Detail = () =>{
const location = useLocation();
return (
<div>
<h1>Detail Page</h1>
<p>{location.state.link}</p>
</div>
);
}
export default Detail();
You can read more about react-router hooks here.

using ref.current as container in react portal throws Target container is not a DOM element

I am using react portal to render inside dom element rendered by parent element
import { useRef, useState } from "react";
import { createPortal } from "react-dom";
export default function App() {
const pageTitleRef = useRef(null);
const [page, setPage] = useState(true);
return (
<div className="App">
<header>
<h1>
App-<span id="page-title" ref={pageTitleRef}></span>
</h1>
<button
onClick={(e) => {
setPage((p) => !p);
}}
>
toggle page
</button>
</header>
{page ? (
<Page1 key="page1" pageTitleRef={pageTitleRef} />
) : (
<Page2 key="page2" pageTitleRef={pageTitleRef} />
)}
</div>
);
}
function Page1({ pageTitleRef }) {
return (
<div className="Page1">
{createPortal("Page2 title", pageTitleRef.current)}
<h2>Page 1 content!</h2>
</div>
);
}
function Page2({ pageTitleRef }) {
return (
<div className="Page2">
{createPortal("Page1 title", pageTitleRef.current)}
<h2>Page 2 content!</h2>
</div>
);
}
however it throws an error Target container is not a DOM element.
https://codesandbox.io/s/xenodochial-mendeleev-b5pbr?file=/src/App.js
The problem is that you're trying to use pageTitleRef.current before it has anything in it. It doesn't have anything in it until App renders, but during the rendering of App, you're trying to call createPortal (from Page1 and Page2).
You'll need to do some refactoring; some options:
Render normally in that area, rather than having Page1 and Page2 reach out to it via createPortal; App already knows what page it's on.
Put the portal area in your skeleton HTML, rather than rendering it with App.
Hold the content that should be in the header in App state and pass a setter function to Page1 and Page2 they can use to set that content.
I'd go with #1, Page1 and Page2 seem to have too many responsibilities (page header and page body). You could split them up into PageHead1/PageHead2 and PageBody1/PageBody2. But if it makes more sense, either #2 or #3 could work too.

I got Cannot read property 'name_restaurant' of undefined When I tried to return thedetails of each restaurant on my detail page

I've created a dynamic routing for a ProductDetail page (each Restaurant has its own details that should be shown on this page). the routing is working but I don't get any data and I can't figure out the right way to get data from firestore by the Id of each restaurant.
PS: the product details are rendering in the console but the problem still how to display to the detailpage
ProductDetail.js
import { firestore } from "../../../../../fire";
import {useParams} from "react-router-dom";
import {useEffect} from "react";
import {useState} from "react";
import {Fragment} from "react";
function ProductDetail() {
const {productId}= useParams();
const [product,setProduct]=useState();
useEffect( () => {
firestore
.collection("Restaurants")
.doc(productId).get()
.then( doc => {
console.log(doc.data());
setProduct(doc.data());
});
}, () => {
}
);
return (
<div className="col-12">
<div className="card">
<h1>{product.name_restaurant} </h1>
<p>Price:$</p>
{/* <p>{product.email}</p> */}
</div>
</div>
);
}
export default ProductDetail;
this my console : all details of the restaurant are returned
Still cannot return details on my page
I got this error
This happens because when you first render the component your product state variable is null, then you make the API call in the useEffect, your state variable is populated and the component is re-rendered, but at that point, you already have the error. To fix it, you just have to render your markup when the product is ready to be rendered (not null)
return product ? (
<div className="col-12">
<div className="card">
<h1>{product.name_restaurant} </h1>
<p>Price:$</p>
{/* <p>{product.email}</p> */}
</div>
</div>
) : <div>Loading...</div>;
SOLVED By adding an ? to product and that's to check undefined {product?.name_restaurant}

Show a skeleton placeholder until Facebook Comments component loads completely in React (Next.js)

Using Next.js, I want to show a skeleton placeholder until Facebook Comments component loads completely.
Here is the code.
import { useState, useEffect } from "react";
import { initFacebook } from "../utils/initFacebook";
export default function IndexPage() {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
const loadFacebook = async () => {
await initFacebook();
setLoaded(true);
};
loadFacebook();
}, []);
const skeletonComponent = (
<div>
<h1>Some skeleton placeholder</h1>
</div>
);
const facebookComponent = (
<div
className="fb-comments"
data-href="https://developers.facebook.com/docs/plugins/comments#configurator"
data-width="580"
data-numposts="10"
/>
);
return (
<div>
{loaded ? facebookComponent : skeletonComponent}
</div>
);
}
I'm using the state to switch between two components.
But the skeleton component does not wait until the Facebook component is fully loaded, and therefore users see the blank screen for about 3-5 seconds.
How should I go about having the skeleton component wait out until the Facebook component is visible?
The full code is available on CodeSandbox.
Any help would be appreciated.

Reactjs: On clicking title display blog post contents

I'm trying to make a blogsite using MERN stack. So here I have my Blogs component which fetches all blogposts from db and display as shown below
here's the react code for the above pic
import { useEffect, useState } from "react";
import axios from 'axios';
import BlogDetails from "./BlogDetails";
const Blogs = () => {
const [blogPost, setPosts] = useState([])
useEffect(()=>{
const fetchBlog = async ()=>{
const blogsData = await axios('http://localhost:4000/blogs')
const blogsFetched = blogsData.data
console.log(blogsFetched);
setPosts(blogsFetched)
}
fetchBlog()
},[])
return (
<div className="blogs content">
<h2>All Blogs</h2>
{ blogPost.map((post)=>(
<div key={post._id}>
<a className="single" href={post._id}>
<h3 className="title" >{post.title}</h3>
<p className="snippet">{post.snippet}</p>
</a>
</div>
))
}
</div>
);
}
export default Blogs;
I want to show a particular blog post in detail when I click on its title from the above page.
On clicking the title, those details will be passed as props to another component named BlogDetails and will be rendered. The part where I'm stuck is routing to BlogDetails component with blogpost id. Is there any way in which on clicking the title, I can use Route to render BlogDetails component?
Please help
Assuming you're using react-router, you could do something like:
const history = useHistory();
.
.
.
<div onClick={() => history.push(`/blog/${post.id}`)}>
<h3 className="title">{post.title}</h3>
</div>
And then your router will render your component which you specified to be rendered for the /blog route. In that route, you can fetch the url using useLocation and then parse the id by splitting the url.
const { pathName } = useLocation(); // also from react-router
Alternatively (and more elegantly), you can push pieces of state when you do history.push, like:
...onClick={() => history.push(`/blog${post.id}`, { blogId: ${post.id}})}
Then again in your subsequently rendered component you use useLocation to get the state passed:
const { state } = useLocation();
yes you can use route for BlogDetail component
add BlogDetail link in BrowserRouter
<Route path={'url'} render={(props) => <BlogDetail/>}/>
In Blogs component use Link of react router dom and then use this link on title in render html like this
import { Link } from "react-router-dom";
{ blogPost.map((post)=>(
<div key={post._id}>
<a className="single" href={post._id}>
<Link to={{
pathname:'url', // this will be your url of BlogDetail
state: post //if you want post object detail in next component
}}>
<h3 className="title" >{post.title}</h3>
</Link>
<p className="snippet">{post.snippet}</p>
</a>
</div>
))
}

Categories