`setState` not synced with UI - javascript

I am making a forum like website with a component to display different forums with a pagination button to show the next page using Next.js.
My current page implementation is that I am going to query data using getServerSideProps for initial page load. After that when user clicks on Next page button to show next page of the forum.
I wanted to query the same API that I have called in getServerSideProps to get the next page of the data.
I managed to get the button works. But the problem is that the number of page that is set by setPage doesn't seem to be synced up with the UI.
This is the code:
import dayjs from 'dayjs'
import type { GetServerSidePropsResult } from 'next'
import Head from 'next/head'
import Link from 'next/link'
import { useState } from 'react'
import type { GetPostListRequestQueries } from '#/types/GetPostListRequestQueries'
import type { GetPostListResponseBody } from '#/types/GetPostListResponseBody'
export async function getServerSideProps (): Promise<GetServerSidePropsResult<{ announcements: GetPostListResponseBody }>> {
const queries: GetPostListRequestQueries = {
announcement: true,
category_based_announcement: false,
page: 1
}
try {
const url = new URL('http://127.0.0.1:5155/forum/posts')
const searchParams = new URLSearchParams(queries as Record<string, string>)
url.search = searchParams.toString()
const response = await fetch(url)
const announcements: GetPostListResponseBody = await response.json()
return {
props: {
announcements
}
}
} catch (e) {
const response: GetPostListResponseBody = {
posts: []
}
return {
props: {
announcements: response
}
}
}
}
function Forum ({ announcements }: { announcements: GetPostListResponseBody }): JSX.Element {
const [page, setPage] = useState(1)
const [pageAnnouncements, setPageAnnouncements] = useState(announcements)
const fetchAnnouncements = async (): Promise<void> => {
const queries: GetPostListRequestQueries = {
announcement: true,
category_based_announcement: false,
page
}
try {
const url = new URL('http://127.0.0.1:5155/forum/posts')
const searchParams = new URLSearchParams(queries as Record<string, string>)
url.search = searchParams.toString()
const response = await fetch(url)
const announcements: GetPostListResponseBody = await response.json()
setPageAnnouncements(announcements)
} catch (e) {
const response: GetPostListResponseBody = {
posts: []
}
setPageAnnouncements(response)
}
}
async function goToPreviousPage (): Promise<void> {
setPage(() => page - 1)
await fetchAnnouncements()
}
async function goToNextPage (): Promise<void> {
setPage(() => page + 1)
await fetchAnnouncements()
}
return (
<>
<Head>
<title>Forum • ger</title>
<meta name="description" content="reg spelled backwards" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<main className="container mx-auto">
<h1 className="text-4xl text-current font-bold">Forum {page}</h1>
<div className="flex flex-row justify-between">
<h3 className="text-2xl text-current">Global announcements</h3>
<div className="flex flex-row btn-group">
<button className="btn" onClick={goToPreviousPage}>Previous page</button>
<button className="btn" onClick={goToNextPage}>Next page</button>
</div>
</div>
<div className="overflow-x-auto">
<table className="table w-full">
<thead>
<tr>
<td>Topic</td>
<td>Replies</td>
<td>Views</td>
<td>Activity</td>
</tr>
</thead>
<tbody>
{
pageAnnouncements.posts.map(a => {
return (
<tr key={a.id}>
<td>
<Link className="font-bold link link-hover" href={{ pathname: '/forum/posts/[postId]', query: { postId: a.id } }}>{a.name}</Link>
<div className="flex flex-row">
<Link className="text-sm opacity-75 link link-hover" href={{ pathname: '/forum/users/[username]', query: { username: a.username } }}>
{a.username}
</Link>
<p className="text-sm opacity-75">
• {dayjs(a.created_timestamp).format('MMMM D, YYYY HH:mm')}
</p>
</div>
</td>
<td>10</td>
<td>{a.view_count}</td>
<td>1h</td>
</tr>
)
})
}
</tbody>
</table>
</div>
<h3 className="text-2xl text-current font-bold">Trending</h3>
<div className="overflow-x-auto">
<table className="table w-full">
<tbody>
<tr>
<td>Cy Ganderton</td>
<td>Quality Control Specialist</td>
<td>Blue</td>
</tr>
</tbody>
</table>
</div>
</main>
</>
)
}
export default Forum
You can see from the gif below that the number next to the word "Forum" and the number used when query is off by 1 all the time.
I am new to Nextjs and doesn't understand this kind of behavior. Thank you for any help.

Explanation
Calling setState does'nt immedialty update the related state. A re-render is needed. This how setState works by design. As the doc says:
The set function only updates the state variable for the next render. If you read the state variable after calling the set function, you will still get the old value that was on the screen before your call.
In goToPreviousPage and goToNextPage you are calling setPage and immediately fetchAnnouncements, so it's using the value of page from the previous render.
Solution
What you can do to make it work with little changes is to pass page to fetchAnnouncements as parameter, like so:
const fetchAnnouncements = async (page: number): Promise<void> => {
// ...
}
async function goToPreviousPage (): Promise<void> {
setPage(() => page - 1)
await fetchAnnouncements(page - 1)
}
async function goToNextPage (): Promise<void> {
setPage(() => page + 1)
await fetchAnnouncements(page + 1)
}

Related

REACT JS, how to DELETE DATA without refreshing page using useLoaderData() react router v6++

My goal is when I delete some data on my table it prevent from refreshing using the new version of userLoadData from react-router-dom#6. I don't know how to do that I already tried to ask CHATGTP but the solution they gave was not helping me.
I tried this I created some function products() with inside useLoaderData, it's working and deleting the data, but the problem is it's not refreshing when I delete. I need to refresh the page to update.
I will show my code that I've tried. Here is my component Product with my export const ProductData.
import { useLoaderData } from "react-router-dom";
import axiosClient from "../../../axios-client"
export const ProductData = () => {
return axiosClient.get('admin/product')
.then(({ data }) => {
return data.data
})
.catch((error) => {
return error
})
}
export default function Product() {
const products = () => {
return useLoaderData()
}
const ProductDelete = async(id) => {
await axiosClient.delete('admin/product/delete/'+id)
.then((data) => {
console.log(data)
products()
})
}
return (
<div className="flex flex-col">
<table className="min-w-full divide-y divide-gray-500">
<thead className="bg-red-500">
<tr>
<th>id</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{products() && products().map((product) => (
<tr key={product.id}>
<td className="px-6 py-4 text-sm font-medium text-gray-800 whitespace-nowrap">
<button onClick={()=>ProductDelete(product.id)}>Delete</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
Here is my router
import Product, { ProductData } from "./components/admin/product/IndexProduct"
const router = createBrowserRouter([
{
path: "product",
element: <Product />,
loader: ProductData
}
]);
The basic gist is that you should also provide a route action that issues the DELETE request, which will trigger the Route to revalidate itself and call the loader and refetch updated data.
Example:
import { useLoaderData } from "react-router-dom";
import axiosClient from "../../../axios-client"
export const productsLoader = async () => {
try {
const { data } = await axiosClient.get('admin/product')
return data.data;
} catch (error) {
return error;
};
};
export const deleteProductAction = async ({ request }) => {
const formData = await request.formData();
const id = formData.get("productId");
axiosClient.delete(`admin/product/delete/${id}`);
};
Import the Form component and wrap the delete button to submit the form and trigger the form/route action. Below uses a hidden form field to "inject" the product id that you want to work with.
import { Form, useLoaderData } from "react-router-dom";
export default function Product() {
const products = useLoaderData();
return (
<div className="flex flex-col">
<table className="min-w-full divide-y divide-gray-500">
<thead className="bg-red-500">
<tr>
<th>id</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{products.map((product) => (
<tr key={product.id}>
<td className="px-6 py-4 text-sm font-medium text-gray-800 whitespace-nowrap">
<Form method="delete" replace>
<input name="productId" defaultValue={product.id} hidden />
<button type="submit">Delete</button>
</Form>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
Import the loader and action functions and add them to the "/product" route
import Product, {
deleteProductAction,
productsLoader
} from "./components/admin/product/IndexProduct"
const router = createBrowserRouter([
{
path: "product",
element: <Product />,
loader: productsLoader,
action: deleteProductAction,
}
]);
Demo using "https://jsonplaceholder.typicode.com/" APIs.

How to shorten url from Next JS

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.

Next.js Link doesn't enforce component render, serverSideProps doesn't get updated

so i have this issue with next.js, When my user tries to go from one profile to another via Link in navbar:
<li>
<Link href={`/profile/${user.user.id}`}>
<a className="flex flex-row items-center">
<BiUserCircle />
<span className="ml-1">Profile</span>
</a>
</Link>
</li>
Next doesn't seem to re-render the profile component which is unfortunate because I'm pulling initial profile data in getServerSideProps which leads to weird behavior like when the initial useState data is saved from last visited profile. How can I ensure that each time user visits the profile page brand new initial data is sent to useState?
My profile page looks like this:
const Profile = ({ initialProfile, posts }) => {
const router = useRouter();
const [profile, setProfile] = useState(initialProfile);
const { userId } = router.query;
const dispatch = useDispatch();
useEffect(() => {
// This is my current solution, however it feels really hacky
fetchProfile();
}, [userId]);
const fetchProfile = async () => {
const resp = await axios.get(apiURL + `accounts/users/${userId}`);
setProfile((prof) => ({ ...prof, ...resp.data }));
};
return (
<Layout>
<ProfileSummary
isUser={isUser}
isFollowed={isFollowed}
handleFollow={handleFollow}
profile={profile}
/>
{isUser ? (
<div className="flex justify-center">
<Button colorScheme="green">Add post</Button>
</div>
) : null}
<div className="w-full flex justify-center flex-col items-center pl-2 pr-2">
{posts
? posts.map((post) => (
<Card
key={post.id}
author={post.author}
likes={post.likes}
desc={post.description}
img={post.image}
isLiked={post.is_liked}
postId={post.id}
comments={post.comments}
/>
))
: "No Posts found"}
</div>
</Layout>
);
};
export async function getServerSideProps(context) {
const { userId } = context.query;
const profileResp = await axios.get(apiURL + `accounts/users/${userId}`);
const postsResp = await axios.get(
apiURL + `posts/?author__id=${profile.id}`
);
const profile = profileResp.data;
const posts = postsResp.data;
return {
props: {
initialProfile: profile,
posts,
},
};
}
export default Profile;
I'd really love any help, maybe I should change my overall approach?
The whole project can be found here:
https://github.com/MaciejWiatr/Nextagram/tree/develop
Dont worry this is an known bug, you can read more about it here https://github.com/vercel/next.js/issues/10400. Hopefully it will be fixed soon.

How do I get the inner html based of the position of the on click?

I new to react to so an explanation would be great to your solution, I have a table that renders data from my database, I have a function delete question seen in the image below, I need the question id the number to the right of the button. The problem is I cant get the questionid based off the button clicked in the table. Say for example I click the delete button I want the login id sent as an argument to my function deletequestion. Any method is ok, Feel free to change any existing code if necessary.
Component that renders the table.
import React, { useState } from 'react';
import { Button } from 'semantic-ui-react';
import { currentloginid } from '../login/loginid.js';
import { deletequestion } from '../question/deletequestion.js';
export const ViewQuestionComponent = () => {
let [state, setState] = useState([]);
const handleViewQuestion = async () => {
try {
const response = await fetch('http://localhost/gotaquestion/api/api.php?action=viewquestion', {
method: 'GET',
credentials: 'include'
});
const data = await response.json();
const result = await data;
setState(data);
} catch (e) {
console.log(e);
}
}
return (
<div>
<ViewQuestion onClick={handleViewQuestion} />
<div id="questions">
<Table rows={state}>
<DeleteButton onClick={deletequestion} />
</Table>
</div>
</div>
);
};
export function ViewQuestion({onClick}) {
return (
<Button onClick={onClick}>View Question</Button>
);
}
export default ViewQuestion;
const Table = ({rows, children}) => (
<table className="ui single line table">
<tbody>
{ rows.map(row =>
<tr key={row.questionid}>
<td>{row.question}</td>
<td>{row.timestamp}</td>
<td>{row.catagories}</td>
<td>{(row.answer === null ? "Not Answered" : row.answer)}</td>
<td>{children}</td>
<td>{row.questionid}</td>
</tr>
)}
</tbody>
</table>
);
const DeleteButton = ({onClick}) => (
<button className="ui negative basic button" onClick={onClick}>Delete Question </button>
);
The deletequestion function
export function deletequestion() {
//I need the question id saved in the varible below
var questionid = ;
console.log(questionid);
fetch('http://localhost/gotaquestion/api/api.php?action=deletequestion',
{
method: 'GET',
credentials: 'include'
}
)
}
Thanks is advance :)
First of all, pass the questionId as props to the children like below,
<td>{React.cloneElement(children, { questionid: row.questionid })}</td>
Table Changes:-
const Table = ({ rows, setIdTobeDeleted, children }) => (
<table className="ui single line table">
<tbody>
{rows.map((row) => (
<tr key={row.questionid}>
<td>{row.question}</td>
<td>{row.timestamp}</td>
<td>{row.catagories}</td>
<td>{row.answer === null ? "Not Answered" : row.answer}</td>
<td>
{React.cloneElement(children, { questionid: row.questionid })} //<<<---Here is change
</td>
<td>{row.questionid}</td>
</tr>
))}
</tbody>
</table>
);
Once the questionid passed as a property to the child (DeleteButton), access the id and pass to the onClick method.
const DeleteButton = ({ questionid, onClick }) => (
<button
className="ui negative basic button"
onClick={() => onClick(questionid)} //<<<<---- see here
>
Delete Question{" "}
</button>
);
Working code - https://codesandbox.io/s/angry-feistel-lxp09?file=/src/App.js
This should work
export function deletequestion(event) {
var questionid = event.target.closest("td").nextElementSibling.innerHTML ;
console.log(questionid);
fetch('http://localhost/gotaquestion/api/api.php?action=deletequestion',
{
method: 'GET',
credentials: 'include'
}
)
}

Next JS build isn't building out every path

Summary / Issue
I've created an anime database app using Nextjs with deployment on Vercel. The build was fine and the initial page rendered, but only a few of my dynamic routes are being rendered, the rest display a 404 page. I went into the deploy log and found that for each dynamic page, only 10 routes were being built for every dynmaic route.
Deploy Screenshot from Vercel
While working in development (localhost:3000), there were no issues and everything ran fine.
The routes are based on the id of each title and there are thousands of titles.
My Code
Here is my code for one of the pages using getStaticPaths and getStaticProps
export const getStaticProps = async ({ params }) => {
const [anime, animeCharacters, categories, streaming, reviews] = await Promise.all([
fetch(`https://kitsu.io/api/edge/anime/${params.id}`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/characters`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/categories`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/streaming-links`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/reviews`),
])
.then((responses) =>
Promise.all(responses.map((response) => response.json()))
)
.catch((e) => console.log(e, "There was an error retrieving the data"))
return { props: { anime, animeCharacters, categories, streaming, reviews } }
}
export const getStaticPaths = async () => {
const res = await fetch("https://kitsu.io/api/edge/anime")
const anime = await res.json()
const paths = anime.data.map((show) => ({
params: { id: show.id },
}))
return { paths, fallback: false }
}
[id] is my dynamic route and as you can see, it's only being populated with 10 routes (the first 3 and 7 additional).
Despite the number of shows, I'm looping over each show and grabbing its ID and then passing that as the path.
What I've thought of
The API I'm using is the Kitsu API.
Within the docs, it states: "Resources are paginated in groups of 10 by default and can be increased to a maximum of 20". I figured this might be why 10 paths are being generated, but if that was the case, then why would it work fine in production and in deployment? Also, when I click on each poster image, it should bring me to that specific title by its id, whihc is dynamic, so it shouldn't matter how many recourses are being generated initially.
Code for dynamic page `/anime/[id]
import { useState } from "react"
import { useRouter } from 'next/router'
import fetch from "isomorphic-unfetch"
import formatedDates from "./../../helpers/formatDates"
import Navbar from "../../components/Navbar"
import TrailerVideo from "../../components/TrailerVideo"
import Characters from "./../../components/Characters"
import Categories from "../../components/Categories"
import Streamers from "../../components/Streamers"
import Reviews from "../../components/Reviews"
const Post = ({ anime, animeCharacters, categories, streaming, reviews}) => {
const [readMore, setReadMore] = useState(false)
const handleReadMore = () => setReadMore((prevState) => !prevState)
let {
titles: { en, ja_jp },
synopsis,
startDate,
endDate,
ageRating,
ageRatingGuide,
averageRating,
episodeCount,
posterImage: { small },
coverImage,
youtubeVideoId,
} = anime.data.attributes
const defaultImg = "/cover-img-default.jpg"
const synopsisSubString = () =>
!readMore ? synopsis.substring(0, 240) : synopsis.substring(0, 2000)
const router = useRouter()
if(router.isFallback) return <div>loading...</div>
return (
<div className='relative'>
<div className='z-0'>
<img
className='absolute mb-4 h-12 min-h-230 w-full object-cover opacity-50'
src={!coverImage ? defaultImg : coverImage.large}
/>
</div>
<div className='relative container z-50'>
<Navbar />
<div className='mt-16 flex flex-wrap md:flex-no-wrap'>
{/* Main */}
<div className='md:max-w-284'>
<img className='z-50 mb-6' src={small} />
<div className='xl:text-lg pb-6'>
<h1 className='mb-2'>Anime Details</h1>
<ul>
<li>
<span className='font-bold'>Japanese Title:</span> {ja_jp}
</li>
<li>
<span className='font-bold'>Aired:</span>{" "}
{formatedDates(startDate, endDate)}
</li>
<li>
<span className='font-bold'>Rating:</span> {ageRating} /{" "}
{ageRatingGuide}
</li>
<li>
<span className='font-bold'>Episodes:</span> {episodeCount}
</li>
</ul>
</div>
<Streamers streaming={streaming} />
</div>
{/* Info Section */}
<div className='flex flex-wrap lg:flex-no-wrap md:flex-1 '>
<div className='mt-6 md:mt-40 md:ml-6 lg:mr-10'>
<h1 className='sm:text-3xl pb-1'>{en}</h1>
<h2 className='sm:text-xl lg:text-2xl pb-4 text-yellow-600'>
{averageRating}{" "}
<span className='text-white text-base lg:text-lg'>
Community Rating
</span>
</h2>
<div>
<p className='max-w-2xl pb-3 overflow-hidden xl:text-lg'>
{synopsisSubString()}
<span className={!readMore ? "inline" : "hidden"}>...</span>
</p>
<button
className='text-teal-500 hover:text-teal-900 transition ease-in-out duration-500 focus:outline-none focus:shadow-outline'
onClick={handleReadMore}
>
{!readMore ? "Read More" : "Read Less"}
</button>
</div>
<Categories categories={categories} />
<Reviews reviews={reviews}/>
</div>
{/* Sidebar */}
<section className='lg:max-w-sm mt-10 md:ml-6 lg:ml-0'>
<TrailerVideo youtubeVideoId={youtubeVideoId} />
<Characters animeCharacters={animeCharacters} />
</section>
</div>
</div>
</div>
</div>
)
}
export const getStaticProps = async ({ params }) => {
const [anime, animeCharacters, categories, streaming, reviews] = await Promise.all([
fetch(`https://kitsu.io/api/edge/anime/${params.id}`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/characters`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/categories`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/streaming-links`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/reviews`),
])
.then((responses) =>
Promise.all(responses.map((response) => response.json()))
)
.catch((e) => console.log(e, "There was an error retrieving the data"))
return { props: { anime, animeCharacters, categories, streaming, reviews } }
}
export const getStaticPaths = async () => {
const res = await fetch("https://kitsu.io/api/edge/anime")
const anime = await res.json()
const paths = anime.data.map((show) => ({
params: { id: show.id },
}))
return { paths, fallback: true }
}
export default Post
Screenshot of Errror
Repo
If the API you are working with serves resources in groups of 10, then when you call the API in getStaticPaths you only have 10 id ahead of time. Using static generation in nextjs builds static pages for all the available ids ahead of time in production mode. But when in development mode your server will recreate each page on per request basis. So to solve this problem you can build the first 10 pages and make the rest of the pages to be fallback. Here is how you do it.
export const getStaticPaths = async () => {
const res = await fetch("https://kitsu.io/api/edge/anime")
const anime = await res.json()
// you can make a series of calls to the API requesting
// the next page to get the desired amount of data (100 or 1000)
// how many ever static pages you want to build ahead of time
const paths = anime.data.map((show) => ({
params: { id: show.id },
}))
// this will generate 10(resource limit if you make 1 call because your API returns only 10 resources)
// pages ahead of time and rest of the pages will be fallback
return { paths, fallback: true }
}
Keep in mind when using {fallback: true} in getStaticPaths you need have some sort of loading indicator because the page will be statically generated when you make a request for the first time which will take some time(usually very fast).
In your page that you want to statically generate
function MyPage = (props) {
const router = useRouter()
if (router.isFallback) {
// your loading indicator
return <div>loading...</div>
}
return (
// the normal logic for your page
)
}
P.S. I forgot to mention how to handle errors where API responds with 404 or 500 and the resource doesn't exist to send as props when using fallback in static generation.
So here's how to do it.
const getStaticProps = async () => {
// your data fetching logic
// if fail
return {
props: {data: null, error: true, statusCode: 'the-status-code-you-want-to-send-(500 or 404)'}
}
// if success
return {
props: {data: 'my-fetched-data', error: false}
}
}
// in the page component
import ErrorPage from 'next/error';
function MyStaticPage(props) {
if (props.error) {
return <ErrorPage statusCode={404}/>
}
// else you normal page logic
}
Let me know if it helped or you encountered some error while implementing.
Here is where you can learn more https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation

Categories