Why does dynamic page shows the same data per request in Next.js? - reactjs

I have a dynamic page named [className].js. In it, I'm trying the fetch the data using getServerSideProps. Currently, I have only two sets of data. When I click a link, that's supposed to take me the dynamic page with the className and show the data within it. But, no matter which link I click, it always shows the data of the first one.
Why is this happening? How can I get the data of the specified className?
export default function classPage({ classDetail }) {
const capitalizedClassTitle =
classDetail.title.charAt(0).toUpperCase() + classDetail.title.slice(1);
const classTitle = capitalizedClassTitle.replace(/-/g, " ");
return (
<div>
<NavBar />
<div>This is the page for {classTitle}</div>
<div className="w-20 h-20">
<Image
src={classDetail.classImageURL}
width={classDetail.imageWidth}
height={classDetail.imageHeight}
/>
</div>
</div>
);
}
export async function getServerSideProps({ query: { className } }) {
const res = await fetch(
`http://localhost:3000/api/admin/classes/${className}`
);
const { data } = await res.json();
return { props: { classDetail: data } };
}

I found the solution. In the api pages of [className], I had this line of code wrong.
const classDetail = await Class.findOne({className});
I changed it to await Class.findOne({title: className}) and now it is working as I wished. title was the keyword for className in my Class schema.

Related

React Query w/NextJS - I'm noticing that, once my page has loaded, the query does not execute until I have clicked within the browser's viewport

I'm new to React Query and am wondering what that's about--forestalling execution of my GraphQL query until I click anywhere within the browser's viewport. Shouldn't the query just execute straight away?
Here's my code:
import { useUrlHashParameters } from "../src/hooks";
import { useQuery } from "react-query";
import { gql, request } from "graphql-request";
export default function Home() {
const parameters = useUrlHashParameters();
const { data } = useQuery("test", async () => {
if (parameters) {
localStorage.setItem("accessToken", parameters.accessToken);
const url = "http://localhost:3001/graphql";
const document = gql`
query {
products {
id
title
description
totalInventory
__typename
}
}
`;
const requestHeaders = {
Authorization: `Bearer ${localStorage.getItem("accessToken")}`,
};
return await request({
url,
document,
requestHeaders,
});
}
});
console.log(data);
return (
<div className={styles.container}>
<main className={styles.main}>
<Image
src="/blah-logo.png"
alt="Blah"
width={300}
height={75}
/>
{data?.products.map(({ title }, index: number) => (
<div key={index}>{title}</div>
))}
</main>
</div>
);
}
Shouldn't the query just execute straight away?
Yes, it should, and it likely does. My best guess is that on the first render, the parameters are undefined, and since you check for if (parameters) in the queryFn without doing anything in an else branch, the queryFn will just return a Promise that resolves to undefined (which will be illegal in v4 btw).
Then, when you focus the window, you get a refetch, in which case parameters likely exist.
You can verify this by putting a log statement into the queryFn, but outside the if statement.
Further, all parameters that your query uses should be part of the query key. That makes sure that you get automatic refetches when the parameters are changing. So changing your query key to:
["test", parameters]
and setting: enabled: !!parameters instead of the if to stop the query from running if there are no parameters is likely the best course of action:
const parameters = useUrlHashParameters();
const { data } = useQuery(
["test", parameters],
() => request({...}),
{
enabled: !!parameters
}
)

How can i use an AXIOS POST reponse from one component in another component?

Everything seems in order, the API is already functioning perfectly and returning a response with a JSON.
I need to get this JSON from the component that makes the post and gets the response to a component made to order this info and render it.
So far I have been able to access the response and get the arrays of information in the same component where the Axios post is, whenever I try to use the JSON from another component it won't recognize it
Should I combine these two components and make one? also, I have seen people using redux to store props globally and then using it in all of the ccomponents with no problem of recognition or prop-drilling.
I have read docs about redux. I understand how it works but as new as I am in it,I don't have a clue how to implement it
UPLOADER AND AXIOS POST COMPONENT
class SendFile extends Component {
state = {
// Initially, no file is selected
selectedFile: null
};
// On file select (from the pop up)
onFileChange = event => {
// Update the state
this.setState({ selectedFile: event.target.files[0] });
};
// On file upload (click the upload button)
onFileUpload = async () => {
// Create an object of formData
const formData = new FormData();
// Update the formData object
formData.append(
"file",
this.state.selectedFile,
);
// Details of the uploaded file
console.log(this.state.selectedFile);
// Request made to the backend api
// Send formData object
const headers = {
'access-token': "ACCES-TOKEN",
'filename': "file-name"
}
const diagnoseRequest = await axios.post("API",formData,{
headers:headers
});
console.log(diagnoseRequest.data.diagnose);
};
// File content to be displayed after
// file upload is complete
fileData = () => {
if (this.state.selectedFile) {
return (
<div>
<p>Nombre del archivo: {this.state.selectedFile.name}</p>
</div>
);
} else {
return (
null
);
}
};
render() {
return (
<Container>
<Uploader>
<input id="uploadBtn" accept="application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document" type="file" onChange={this.onFileChange} />
<button onClick={this.onFileUpload}>
Cargar Archivo
</button>
</Uploader>
{this.fileData()}
</Container>
);
}
}
export default SendFile;
RENDERING COMPONENT
function PdfSection({divider,img, title, data, text, subtext, text2, subtext2, text3, subtext3}) {
return (
<Container>
<Top>
<img className="divider" src={divider}/>
</Top>
<Bottom>
<LeftSide>
<img className='description' src={img}/>
</LeftSide>
<RightSide>
<Header>
<h1>{title}</h1>
</Header>
<Body>
<h2>{data}</h2>
<h3>{text}</h3>
<p>{subtext}</p>
<h3>{text2}</h3>
<p>{subtext2}</p>
<h3>{text3}</h3>
<p>{subtext3}</p>
</Body>
</RightSide>
</Bottom>
</Container>
)
}
export default PdfSection
THANKS IN ADVANCE!

Building query with NextJS, Prisma, and ClerkJS

I'm prototyping a project using NextJS, Prisma, and ClerkJS. I'm trying to understand how I would supply various params/props to my Prisma search clause. In particular I need to get the email address of a user from ClerkJS. This is my current index file:
import React from "react";
import prisma from "../../prisma/initPrisma"
const FacilitiesPage = ({ facilities }) => {
return (
<div className={styles.dashCards}>
{facilities.map((facility) => {
return (
<div className={styles.card} key={facility.id}>
<h4>{facility.name}</h4>
</div>
);
})}
</div>
);
};
export async function getStaticProps() {
const facilities = await prisma.facility.findMany({
where: {
ownerEmail: 'harcodedemail'
},
});
return {
props: {
facilities,
},
};
}
export default FacilitiesPage;
Obviously I can't hardcode the email address of every user in the system. ClerkJS offers several ways to query the user object and return various things from it, which I could pass into getStaticProps (or getServerSideProps probably). But nothing I've tried works. Candidly, I'm still learning the "React way" to do a lot of things.
TL;DR: how do I supply props to the query string in getStaticProps?
The folks at Clerk.dev just answered this question. You need to use getServerSideProps and then use the new "withServerSideAuth" component. Here is a snippet from the blog post https://clerk.dev/blog/next-js-ssr-authentication-with-clerk :
import { withServerSideAuth } from "#clerk/nextjs/ssr";
export const getServerSideProps = withServerSideAuth(async ({ req, resolvedUrl }) => {
const {sessionId,getToken} = req.auth;
if (!sessionId) {
return { redirect: { destination: "/sign-in?redirect_url=" + resolvedUrl } };
}
// use a token for your Clerk integrations
const hasuraToken = await getToken({ template: 'hasura' });
// retrieve data from your Hasura integration
return { props: {} };
});

Local storage being overwritten with new data

Hello guys I made a history page so the user can look at their past search history and so I used localstorage. On the history page, I am trying to render data that stays there and isn't changed when I go to search the api again. Instead I want it to keep adding data to the page. I was thinking the data would just keep being added to the new array in local storage but it overwrites the existing data with new data. Ive made an attempt to prevent this but I am stuck.
Here is my code of all of my pages
Search page
export default function SearchPage(props) {
// creating state to fetch the api
const [search, setSearch] = useState('')
// this is my function to monitor the words that are put into the input so these keywords that only matches specific data
// in the api and so one the data is fetched it renders the topic related to that keyword
const handleSearch = (event) => {
setSearch(event.target.value)
}
const handleSubmit = (event) => {
event.preventDefault();
// this is where I bring my useState variable to monitor the state of the key words in order to
// target specific data from the api
let url = `http://hn.algolia.com/api/v1/search_by_date?query=${search}`;
axios
.get(url)
.then((response) => {
const result = response.data.hits;
// this pushes the data fetched from the api to the results page using history
props.history?.push ({
pathname: '/results',
state: result,
});
})
// catching any errors to let me know if there is something going on in the .then function
.catch((error) => {
console.log(error)
console.log('Error while fetching data!')
})
}
return (
<div>
<div className="search-form-container">
{/* my form in where I search data by keywords */}
<form onSubmit={handleSubmit}>
<input type='text' placeholder="search" onChange={handleSearch} value={search}/>
<button type="submit">Search</button>
</form>
<hr/>
<Link to="/history">Your Search History</Link>
</div>
</div>
)
}
Results page
export default function ResultsPage(props) {
console.log(props)
// the variable declared below is where I am bringing in the data through history from the api.
const data = props.history.location.state;
let storedData = localStorage.setItem('data', JSON.stringify(data))
console.log(storedData)
// being that I am using history, I can map through the array of objects on the results page as shown below and then render it
const hitList = data.map((data, idx) => {
return (
<ul key={idx}>
<div>
<li> Author: {data.author}</li>
<li>Story Title: {data.story_title}</li>
<li>Comment: {data.comment_text}</li>
<li>Created on: {data.created_at}</li>
<li></li>
</div>
<hr/>
</ul>
)
})
return (
<div>
<Link to='/'><h1>Search</h1></Link>
<Link to='/history'><h1>Your History</h1></Link>
{/* In order for me to show my data I first had to map through the array of objects and then put the variable "hitlist" in the return */}
<h3>{hitList}</h3>
</div>
)
}
History page
export default function SearchHistoryPage(item) {
const storedData = JSON.parse(localStorage.getItem('data'));
storedData.push(item);
localStorage.setItem('data', JSON.stringify(storedData));
console.log(storedData);
const searchHistory = storedData.map((data, idx) => {
return (
<ul key={idx}>
<li> Author: {data.author}</li>
<li>Story Title: {data.story_title}</li>
<li>Comment: {data.comment_text}</li>
<li>Created on: {data.created_at}</li>
</ul>
)
})
return (
<div>
<h2>{searchHistory}</h2>
</div>
)
}
In your Results page, you are overwriting your localStorage 'data' key with only the results fetched from Search page.
What you can do is to fetch your "history" (localStorage 'data') before you push the new results in your Results page, and not in your History page as so:
In Results page:
const data = props.history.location.state;
// ADD THESE 2 LINEs
const historicData = JSON.parse(localStorage.getItem('data'));
historicData.push(data)
// Store "historicData" instead of "data"
// let storedData = localStorage.setItem('data', JSON.stringify(data))
let storedData = localStorage.setItem('data', JSON.stringify(historicData))
console.log(storedData)
In History page:
// Just use localStorage 'data'.
// Do not modify anything in History page since you are not taking any actions here.
const storedData = JSON.parse(localStorage.getItem('data'));
storedData.push(item);
// localStorage.setItem('data', JSON.stringify(storedData)); <-- REMOVE
// console.log(storedData); <-- REMOVE

Next.js: How do you pass data to a route created dynamically

I have a component that is receiving data:
const ProductTile = ({ data }) => {
let { productList } = data
var [products] = productList
var { products } = products;
return (
<div>
<div className="p-10 grid grid-cols-1 sm:grid-cols-1 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-3 gap-5">
{products.reduce((products, product) => products.find(x => x.productId === product.productId) ? products : [...products, product], []).map(({ colorCode, defaultColorCode, now, productId, productCode, productDescription, }, index) => {
return (
<Link key={`${productId}${index}`}
href={{
pathname: '/s7-img-facade/[slug]',
query: { slug: productCode },
}}
passHref>
/* template */
</Link>
)
})}
</div>
</div>
)
}
export default ProductTile
It creates a grid of templates each wrapped in a <Link> component which is rendering a dynamic component;
/s7-img-facade/[product]
What I would like is for the dynamic component to have access to products object which is in the ProductTile .
I know that I can do a getStaticProps in the dynamic component to do another request but that seems redundant and not dry...
Any ideas how the dynamic component get access to the products object?
Thanks in advance!
You've got the right idea - you can pass additional properties in the query field, but you'll need to use getServerSideProps to extract those from the query param and pass it to the page component as props. Something like this:
// pages/product.js
...
<Link key={`${productId}${index}`}
href={{
pathname: '/s7-img-facade/[slug]',
query: {
description: productDescription,
slug: productCode
},
}}
passHref
>
/* template */
</Link>
...
// pages/s7-img-facase/[slug].js
export default function S7ImgFacasePage({ description }) {
return <p>{ description }</p>
}
export const getServerSideProps = ({ params }) => {
const description = { params }
return {
props: {
description
}
}
}
So basically you pass it from the first page in params, read that in getServerSideProps of the second page, and then pass that as a prop to the second page component.
You mentioned getStaticProps - this won't work with static pages because getStaticProps is only run at build time so it won't know anything about the params you send at run time. If you need a fully static site, then you should consider passing it as a url parameter and reading it in useEffect or passing all possible pages through getStaticPaths and getStaticProps to generate all your static pages.

Resources