I'm trying to create pagination with nextjs and the useSWR hook.
This is how I've currently done it, and it appears to be working... however I read in the docs that the key passed as the first parameter should be a unique string (usually a URL). I'm just passing the index to fetch the correct data. Will my approach mess up the caching? I'm not sure if I'm doing this correctly?
index.js
import React, { useState } from 'react'
import Page from '../components/page'
export default function IndexPage( ) {
const [pageIndex, setPageIndex] = useState(0)
return (
<div>
<Page index={pageIndex} />
<button onClick={() => setPageIndex(pageIndex - 1)}>Previous</button>
<button onClick={() => setPageIndex(pageIndex + 1)}>Next</button>
</div>
)
}
And in my page.js
import useSWR from 'swr'
import { fetcher } from '../client/fetcher'
function Page({ index }) {
const { data } = useSWR(index, fetcher)
console.table(data)
return <div>nothing here, just testing</div>
}
export default Page
And finally the fetcher.js
import client from './contentful-client'
export async function fetcher(pageIndex = 1, limit = 3) {
const data = await client.getEntries({
content_type: 'posts',
skip: pageIndex * limit,
order: '-fields.publishDate',
limit,
})
if (data) {
return data
}
console.log('Something went wrong fetching data')
}
You may want to move the Contentful data fetching logic to the server as to not expose credentials and logic to the browser. This could be done using Next.js API routes.
// pages/api/posts.js
import client from '<path-to>/contentful-client' // Replace with appropriate path to file
export default async function handler(req, res) {
const { pageIndex = 1, limit = 3 } = req.query
const data = await client.getEntries({
content_type: 'posts',
skip: pageIndex * limit,
order: '-fields.publishDate',
limit,
})
res.json(data)
}
You could then refactor the code in your page to make a request against the newly created API route, passing the route URL as the key to useSWR.
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then(res => res.json())
function Page({ index }) {
const { data } = useSWR(`/api/posts?pageIndex=${index}`, fetcher)
console.table(data)
return <div>nothing here, just testing</div>
}
export default Page
Related
Get post by ID ( slug ) from prisma when getStaticProps() before page build
So the issue is that I cannot use React hook in getStaticProps. I was going to get slug names with useRouter, then query for post by using the slug (postID), but I learned that I cannot run prisma inside of body components. Then I learned that I can use getStaticProps and getStaticPaths to query the post by its ID before build time.
How do I get N levels of slug names in getStaticProps?.
Code
/post/[...slugs].tsx
My url looks like: localhost:3000/post/postID/PostTitle
such as localhost:3000/post/b513-ad29e3cc67d9/Post%20Title
import { Post, PrismaClient } from '#prisma/client';
import { GetStaticPaths, GetStaticProps } from 'next';
import { useRouter } from 'next/router';
type postByIdProps = {
postById: Post
}
export default function PostDetail({postById}: postByIdProps) {
return (
<>
<div>
{postById.title}
</div>
</>
);
}
export const getStaticProps = async(context: any)=>{
// I can't ues React Hook here, but I don't know how to get slug name without the hook.
const router = useRouter();
const slugs: any = router.query.slugs;
const postId = slugs?.[0].toString()
//Prisma
const prisma = new PrismaClient()
const postById = prisma.post.findUnique({
where: {
postId: postId,
},
})
return postById
}
export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => {
return {
paths: [], //indicates that no page needs be created at build time
fallback: 'blocking' //indicates the type of fallback
}
}
This worked fro me, but if someone can improve this code, more than welcome.
How to Build a Fullstack App with Next.js, Prisma, and PostgreSQL
code
import { Post } from '#prisma/client';
import { GetStaticPaths, GetStaticProps } from 'next';
import prisma from '../api/prisma';
type postByIdProps = {
post: Post
}
export default function PostDetail({post}: postByIdProps) {
console.log("Post here,", post)
return (
<>
<div>
{post.title}
</div>
</>
);
}
export const getStaticProps = async({params}: any)=>{
const postId = params.slugs[0] //gets post's ID
const post = await prisma.post.findUnique({
where:{
postId: String(postId)
},
})
return {
props:{
post
}
}
}
export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => {
return {
paths: [], //indicates that no page needs be created at build time
fallback: 'blocking' //indicates the type of fallback
}
}
I've got a problem with updating data from API in NextJS. I want to increment the value by clicking on the button and then update the API.
My local JSON file is in at http:localhost/api/data
data.js:
export default function handler(req, res) {
res.status(200).json({ value: 0 })
}
I want to update this value from Home component
index.js:
import { useState } from 'react'
export default function Home({data) {
const [currentData, setCurrentData] = useState(data)
return (
<div>
{data.value}
<button onClick={(e) => setCurrentData(currentData.value + 1)}>+</button>
</div>
)
}
export async function getServerSideProps() {
const res = await fetch('http://localhost:3000/api/data')
const data = await res.json()
return {
props: { data },
}
}
Is there any way to do it? If something is unclear feel free to ask :)
I want to use a persistent layout in Next.js and pass some data to it from my dynamic Blog post page.
So for example having this code (from next.js documentation):
// pages/posts/[id].js
function Post({ post }) {
return <p>{post.title}</p>;
}
// Trying to export the post title here
export const postTitle = ({ post }) => {
return post.title;
};
export async function getStaticPaths() {
// Call an external API endpoint to get posts
const res = await fetch("https://.../posts");
const posts = await res.json();
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id },
}));
// We'll pre-render only these paths at build time.
// { fallback: false } means other routes should 404.
return { paths, fallback: false };
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`);
const post = await res.json();
// Pass post data to the page via props
return { props: { post } };
}
export default Post;
How can I grab that {post.title} and use it in my persistent layout component like so:
// SiteLayout.js
import React from "react";
import { postTitle } from "../../pages/posts/[id]";
// Main Page Wrapper
const SiteLayout = ({ children }) => {
return (
<>
{postTitle && <p>{postTitle}</p>}
<main className="layout">{children}</main>
</>
);
};
export default SiteLayout;
I've spent hours trying to make this work so I would really appreciate any pointers! Thank you!
I'm attempting to call a Graph QL Query after receiving data from my useEffect hook. I need the data from the response to use in the Query. Hooks however cannot be called conditionally. If I take away the condition however, loadedAnime will be undefined. How do I get around this restraint?
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import AnimeBanner from "../components/AnimeBanner";
import { useHttpClient } from "../Hooks/http-hook";
import { GetAnimeData } from "../GraphQLFunctions";
import { useQuery } from "#apollo/react-hooks";
import gql from "graphql-tag";
const GET_ANIME_INFO = gql`
query GetAnimeInfo($name: String!) {
Media(search: $name) {
title {
romaji
english
native
userPreferred
}
episodes
id
bannerImage
}
}
`;
const Anime = (props) => {
//Logic for getting anime data from mongoDB (episodes, name, cover image)
const { isLoading, error, sendRequest } = useHttpClient();
const [loadedAnime, setloadedAnime] = useState();
const URLTitle = useParams().URLTitle;
useEffect(() => {
const fetchAnime = async () => {
try {
const responseData = await sendRequest(
"http://localhost:5000/api/anime/" + URLTitle
);
setloadedAnime(responseData.animeData[0]);
} catch (err) {
console.log(err);
}
};
fetchAnime();
}, [sendRequest, URLTitle]);
if (isLoading || error) {
return null;
}
//Logic for getting anime data from anilist (Descriptions, tags, banner, trailer, etc.)
const { apiData, apiLoading, apiError } = useQuery(GET_ANIME_INFO, {
variables: {
name: loadedAnime.anime_name,
},
});
if (apiLoading || apiError) {
return null;
}
return <AnimeBanner src={apiData.Media.bannerImage} />;
};
export default Anime;
Short Answer: You can checkout useLazyQuery instead of useQuery.
Documentation link: https://www.apollographql.com/docs/react/data/queries/#executing-queries-manually
When React mounts and renders a component that calls the useQuery hook, Apollo Client automatically executes the specified query. But what if you want to execute a query in response to a different event, such as a user clicking a button?
The useLazyQuery hook is perfect for executing queries in response to events other than component rendering. This hook acts just like useQuery, with one key exception: when useLazyQuery is called, it does not immediately execute its associated query. Instead, it returns a function in its result tuple that you can call whenever you're ready to execute the query
import React, { useState } from 'react';
import { useLazyQuery } from '#apollo/client';
function DelayedQuery() {
const [dog, setDog] = useState(null);
const [getDog, { loading, data }] = useLazyQuery(GET_DOG_PHOTO);
if (loading) return <p>Loading ...</p>;
if (data && data.dog) {
setDog(data.dog);
}
return (
<div>
{dog && <img src={dog.displayImage} />}
<button onClick={() => getDog({ variables: { breed: 'bulldog' } })}>
Click me!
</button>
</div>
);
}
You can either call the query after the await finishes or you can call your query in another useEffect once you update state after your api call. In general, something like this,
const [state, setState] = useState({})
useEffect(async () => {
const result = await get('/api/blah-blah-blah')
// run your query here now that the await has resolved
}, [someDependency])
or
const [state, setState] = useState({})
useEffect(async () => {
const result = await get('/api/blah-blah-blah')
setState(result)
}, [someDependency])
useEffect(() => {
if(state.id) {
// run the query
}
}, [state.someProp])
Novice here with what I'm sure will prove to be a silly question. I've tried to resolve myself without luck. In Postman the data returned to "localhost:8000/playlists/1" is correct, but I'm unable to repeat this success through the API call from my REact app at:3000. When I console.log out the data I'm getting all Playlists objects, not the single playlist as expected.
Below are my server routes and the call from React.
PlaylistComponent in React
import React, { useState, useEffect } from 'react';
import axios from "axios";
const SeededPlaylist = () => {
const [playlist, setPlaylistData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'/:playlistId'
);
setPlaylistData(result.data);
};
fetchData();
}, []);
console.log(playlist);
return (
<div style={{textAlign: 'center'}}>
{playlist.map(playlist => (
<div key={playlist.id}>
{playlist.title}
</div>
))}
</div>
);
}
export default SeededPlaylist
Playlist Routes from Node
import { Router } from 'express';
const router = Router();
router.get('/', async (req, res) => {
const playlists = await req.context.models.Playlist.findAll({
include: [{
model: req.context.models.Song,
attributes: ['track_name', 'artist_name', 'track_duration'],
through: { attributes: [] }
}],
});
return res.send(playlists);
});
router.get('/:playlistId', async (req, res) => {
const playlist = await req.context.models.Playlist.findByPk(req.params.playlistId, {
include: [{
model: req.context.models.Song,
attributes: ['track_name', 'artist_name', 'track_duration'],
through: { attributes: [], }
}],
});
return res.send(playlist);
});
export default router;
Relevant Routers in server.js
app.use('/playlists', routes.playlist);
app.use('/:playlistId', routes.playlist);
app.use('/songs', routes.song);
app.use('/', routes.spotify);
EDIT: I'm 90% sure I'm not getting a specific ID correctly and need to save my URL or the specific ID to my axios request. Not sure how to do that yet, but I'll keep looking.
EDIT 2:
If anyone else ends up here, the issue was as guessed. I was not correctly targeting the URL in the API call. I ended up grabbing the ID from the URL itself with useParams and then using that was a template literal in the get request.
const { id } = useParams();
//Fetch Playlist data from API with Axios
useEffect(() => {
const fetchData = async () => {
const songUri = [];
const response = await axios(`http://localhost:8000/playlists/${id}`
);
setPlaylistData(response.data);
setSongs(response.data.songs);
for (const spotify_uri of Object.keys(response.data)){
console.log(spotify_uri, response.data[spotify_uri]);
}
}
//Second Array prevents activation on component update
fetchData();
}, []);
Relevant Routers in server.js
app.use('/playlists', routes.playlist);
// app.use('/:playlistId', routes.playlist); This is no need, we must remove this line
app.use('/songs', routes.song);
app.use('/', routes.spotify);