My dynamic route with NextJS will not finish compiling when I test it. It gets stuck and then is killed.
Here's my link route that leads to the dynamic page:
const options = props.response.data.map(r => (
<Link href={`/games/${r.gameID}`}>
<Flex key={r.gameID} borderWidth="1px">
<Box p="2" as="button" borderWidth="1px">
<Image
src={r.thumb}
alt={props.gameID}
width={{ md: 20 }}
height={{ md: 10 }} />
</Box>
<Spacer />
<Box p="2">
<Text>{r.external} - {r.cheapest}</Text>
</Box>
</Flex>
</Link>
))
This leads to my dynamic NextJS page called games that uses the [id] parameter in the URL. I call async getStaticPaths and getStaticProps shown below:
export async function getStaticPaths(){
const router = useRouter()
const gameID = router.query.id
return {
params: {
id: gameID
}
}
}
export async function getStaticProps({ params }) {
// Call an external API endpoint to get game data
// Access route params:
const response = await axios.get(`https://www.cheapshark.com/api/1.0/games?id=${params.id}`)
const gameData = response.json()
return {
props: {
data: gameData,
},
}
}
Then I pass the gameData as a prop to the default exported function. Any ideas where I am going wrong?
Related
I am working in Reactjs and i am using Nextjs framework, Right now i am tyring to fetch data from database using nextjs, But right now i am getting following error
TypeError: Cannot read property 'id' of undefined,How can i remove this ? Here is my current code
import { Box, Heading } from "#chakra-ui/react";
export async function getStaticProps() {
const response = await fetch("https://fakestoreapi.com/products");
const data = await response.json();
return {
props: {
products,
},
};
}
function Test({products}) {
return (
<Box>
{products.map((product) => (
<Box>
<Text> {product.title} </Text>
</Box>
))}
</Box>
);
}
export default Test;
Here is my index.js file
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
import Test from '../components/testing/test'
export default function Home() {
return (
<div className={styles.container}>
<Test/>
</div>
)
}
look i think i know where the problem is :
the first problem is that you are using the getStaticProps function in a components while it can only be used in a page (the files inside the pages/ folder) so we need first to move it to index.js like this
index.js
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
import Test from '../components/testing/test'
export async function getStaticProps() {
const response = await fetch("https://fakestoreapi.com/products");
const products= await response.json(); //<- i changed this becaus it was wrong
return {
props: {
products,
},
};
}
export default function Home({products}) {
return (
<div className={styles.container}>
<Test products={products}/>
</div>
)
}
test.js
import { Box, Heading } from "#chakra-ui/react";
function Test({products}) {
return (
<Box>
{products.map((product) => (
<Box key={product.id}>
<Text> {product.title} </Text>
</Box>
))}
</Box>
);
}
export default Test;
the code above worked for me as it is 'except that my link is different of course'
the second problem is that you were getting your data in the data variable
const data = await response.json();
while returning products variable which is undefined
return {
props: {
products,
},
};
i changed it in your code so it became
const products= await response.json(); //<- i changed this becaus it was wrong
return {
props: {
products,
},
now that should work (it worked in my local envirements)
Notes
i added a key in your map function
<Box>
{products.map((product) => (
<Box key={product.id}>
<Text> {product.title} </Text>
</Box>
))}
</Box>
so it don't give you a warning but thats only possible if your product have an id property so if it gave you an error about id property just remove it.
second notes is that my products is structured like this
[
{
"id": "12346",
"title": " test"
},
{
"id": "154346",
"title": " just"
},
{
"id": "169346",
"title": " another"
},
{
"id": "154326",
"title": " example"
}
]
so if your structur is different it may cause problems
first of all you should pass key value in map function like key={products.id},
and in the next step check part of code
return {
props: {
products,
},
};
do you want to pass products as props or data as props?
and check whether API link https://fakestoreapi.com/products is correct?
in the last step, check response in console.log().
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.
I'm trying to use the github api to get a user's projects and list them in a popup window. I'm having trouble figuring out why async / await isn't working and the data i end up passing is always undefined.
This is how i fetch the data from the api (edited to use for... of):
export default async function GitHubFetch({ userName }) {
let returnArray = [];
let response = await customFetch(
Urls.GitHub + "users/" + userName + "/repos"
);
for (const element of response) {
let project = {};
project.name = element.name;
project.description = element.description;
project.html_url = element.html_url;
let langResponse = await customFetch(element.languages_url);
project.languages = Object.keys(langResponse);
returnArray.push(project);
}
console.log("the array i'm returning from fetch is: ", returnArray);
return returnArray;
}
the console.log of returnArray from this function is:
[{"name":"cthulu_finance","description":"stock-trading application written in react and node.js / express","html_url":"https://github.com/contip/cthulu_finance","languages":["TypeScript","HTML","CSS"]},{"name":"c_structures","description":"collection of data structures in c","html_url":"https://github.com/contip/c_structures","languages":["C"]},{"name":"masm_io_procedures","description":"Low-level implementations of string-to-int and int-to-string in x86 assembly","html_url":"https://github.com/contip/masm_io_procedures","languages":["Assembly"]}]
the array of projects from the above function is used to generate the list of projects by this:
export default function GitHubListDisplay({ projects }) {
let listItems = [];
console.log(projects);
if (Array.isArray(projects)) {
projects.forEach((project, index) => {
listItems.push(
<>
<ListGroup.Item action href={project.html_url}>
{project.name}
</ListGroup.Item>
<ListGroup.Item>{project.description}</ListGroup.Item>
<ListGroup horizontal>{HorizontalList(project.languages)}</ListGroup>
</>
);
});
}
return <ListGroup>{listItems}</ListGroup>;
}
and finally, it's all controlled by this function:
export default function GitHubPopUp({ userName }) {
const [projectData, setProjectData] = useState([]);
useEffect(() => {
async function fetchData() {
setProjectData(await GitHubFetch({ userName }));
console.log("the project data i fetched is: ", projectData);
}
fetchData();
}, []);
return (
<>
<OverlayTrigger
placement="right"
delay={{ show: 250, hide: 5000 }}
overlay={
<Popover>
<Popover.Title as="h3">{`GitHub Projects`}</Popover.Title>
<Popover.Content>
<strong>{userName}'s GitHub Projects:</strong>
{projectData.length > 0 && (
<GitHubListDisplay {...projectData} />
)}
</Popover.Content>
</Popover>
}
>
<Button variant="link">{userName}</Button>
</OverlayTrigger>
</>
);
}
From the main controller function, the state eventually gets set correctly, but if i console.log the projectData state directly after awaiting the Fetch function result, it's undefined.. result is:
the project data i fetched is: []
Additionally, even though i have
{projectData.length > 0 &&
before rendering the GitHubListDisplay component, console.logging the input projects property always results in undefined. The function never ends up displaying anything.
Can anyone please help? I've spent an embarrassing number of hours trying to figure this out.
You cannot use forEach indeed for async await as you desire. Just use a modern for … of loop instead, in which await will work as you expected.
Refer to here
However, best practice is to use Promise.all
export default function GitHubPopUp({ userName }) {
const [projectData, setProjectData] = useState([]);
useEffect(() => {
async function fetchData() {
setProjectData(await GitHubFetch({ userName }));
}
fetchData();
}, []);
useEffect(() => {
console.log("the project data i fetched is: ", projectData);
}, [ projectData ]);
return (
<>
<OverlayTrigger
placement="right"
delay={{ show: 250, hide: 5000 }}
overlay={
<Popover>
<Popover.Title as="h3">{`GitHub Projects`}</Popover.Title>
<Popover.Content>
<strong>{userName}'s GitHub Projects:</strong>
{projectData.length > 0 && (
<GitHubListDisplay {...projectData} />
)}
</Popover.Content>
</Popover>
}
>
<Button variant="link">{userName}</Button>
</OverlayTrigger>
</>
);
}
I have a doubt in my nextjs project.
I added a new route inside my header calls /files and I don't know why takes a long time to load the data when I want to return to the home.
I console.log the request I and notice calls to my API and my INDEX twice, but I'm not sure if it's a problem.
The endpoint with the data is a little slow, but I believe that if I call it inside my pages/index getInitialProps, the data it's loaded in server at the beginning, I am right? and if I am right why is it taking so long to show me the data again?
Here is my code!
Header
import React, { Component } from "react";
class Header extends Component {
render() {
return (
<>
<Navbar collapseOnSelect expand="lg" bg="light" variant="light">
<Navbar.Toggle
aria-controls="responsive-navbar-nav"
style={{ outline: "0", display: 'none' }}
/>
<Navbar.Collapse id="responsive-navbar-nav">
<Nav className="mr-auto"></Nav>
<Nav>
<Link href="/" passHref>
<Nav.Link>
Home
</Nav.Link>
</Link>
<Link href="/files" passHref>
<Nav.Link>
Files
</Nav.Link>
</Link>
</Nav>
</Navbar.Collapse>
</Navbar>
</>
);
}
}
export default Header;
pages/index
import React, { useState, useEffect } from "react";
/* Others */
import Error from "./_error";
import { getDataLatestEvents } from "../helper/api";
/* Components */
import MyComponent from "../components/MyComponent";
/* Bootstrap Components */
import Row from "react-bootstrap/Row";
const Index = props => {
console.log('index*************')
const [contentData, setData] = useState([]);
const res = props.data.data.data;
useEffect(() => {
setData(res);
}, [props]);
if (props.statusCode !== 200) {
return <Error statusCode={props.statusCode} />;
}
return (
<>
<Row>
<StyledContainer>
<MyComponent
data={contentData}
/>
</StyledContainer>
</Row>
</>
);
};
Index.getInitialProps = async ({ res }) => {
try {
let req = await getDataLatestEvents();
return { data: req, statusCode: 200 };
} catch (e) {
res.statusCode = 503;
console.log(`error /pages/index: ${e}`);
return { data: null, statusCode: 503 };
}
};
export default Index;
helper/api
import fetch from "isomorphic-unfetch";
const BASE_URL = "https://myendpoint/api";
export async function getDataLatestEvents() {
const res = await fetch(`${BASE_URL}/eventos?latest`);
let data = await res.json();
console.log('API*************')
return { data: data};
}
This sort of delay is often encountered when using next dev (via yarn dev or npm dev). This is because when using yarn dev, page is rebuild every time it is requested. So when you navigate back to the index page, Next.js first rebuild that page for you and then it is served. That's why there is a delay.
You should not find similar delay in production (when using next build and then next start)
Edit
getInitialProps enables server-side rendering in a page. In case you don't need to fetch any data every time the request is sent or page is reloaded, use getStaticProps instead.
My app uses the React Context Provider to pass down a user profile. In my App component, I have my state defined as:
interface IState {
authUser: any;
userProfile: IProfile;
roles: string[];
}
Within my componentDidMount method, I call out to three different API's using fetch. The results then call setState for the respective entries.
The render part of my App is:
<AuthenticationContext.Provider value={this.state}>
<BrowserRouter>
<div>
<Navigation />
<Switch>
/* Other routes removed for brevity */
<Route exact={true} path={routes.HOME} component={Home} />
</Switch>
<Footer />
</div>
</BrowserRouter>
</AuthenticationContext.Provider>
Within the Home component, I'm using the static Class.contextType entry as so:
public static contextType = AuthenticationContext;
public context!: React.ContextType<typeof AuthenticationContext>;
Then within the componentDidMount method, I'm calling another API with entries from the this.context.userProfile object.
I added console log statements to trace through the lifecycle. When I perform a reload of the page, I get this:
Calling /api/profiles/getAdminStatus/7J4OwwnmQ1fMhavSLeLkDkKe9Kl2
Calling getProfile within App
Calling /api/profiles/7J4OwwnmQ1fMhavSLeLkDkKe9Kl2 within getProfile
Calling /api/profiles/7J4OwwnmQ1fMhavSLeLkDkKe9Kl2 within getLookingFor
Calling loadProfiles
Calling getFilterResults with Userid:
Calling /api/search
About to setState in getProfile within App: UserId: 7J4OwwnmQ1fMhavSLeLkDkKe9Kl2
The getFilterResults is showing a blank Userid entry. However, if I browse to another page and then come back to this page, I get these results:
Calling /api/profiles/7J4OwwnmQ1fMhavSLeLkDkKe9Kl2 within getLookingFor
Calling loadProfiles
Calling getFilterResults with Userid: 7J4OwwnmQ1fMhavSLeLkDkKe9Kl2
Calling /api/search
Based on the messages, I'm certain that the problem is the initial calls to get the current user aren't returning before the Home component loads. However, I don't understand why the component isn't rerendering when the setState is happening.
I added the Consumer Component around the contents of the home page, but that didn't help.
I came up with an idea of pushing the list of results and method up to the Context as well so that I could avoid using the static contextType, but that seems hackish to me.
Any thoughts on what I might have done wrong???
*****Edit*****
This is the Home component:
interface IHomeComponentState {
profiles: IProfileShort[];
hasMore: boolean;
error: boolean;
isLoading: boolean;
}
class HomeComponent extends React.Component<any, IHomeComponentState> {
public static contextType = AuthenticationContext;
public _isMounted = false;
public context!: React.ContextType<typeof AuthenticationContext>;
private currentPage: number = 0;
constructor(props: any) {
super(props);
this.state = {
profiles: [],
hasMore: true,
error: false,
isLoading: false,
};
this.loadProfiles.bind(this);
window.onscroll = () => {
if (this.state.error || this.state.isLoading || !this.state.hasMore) {
return;
}
if (
window.innerHeight + document.documentElement.scrollTop ===
document.documentElement.offsetHeight
) {
this.loadProfiles();
}
};
}
public loadProfiles() {
if (this.context) {
const value = this.context;
// tslint:disable-next-line: no-console
console.log(
'Calling getFilterResults with Userid: ' + value.userProfile.userId,
);
getFilterResults(
value.userProfile.gender,
value.userProfile.lookingForGender,
value.userProfile.minAge,
value.userProfile.maxAge,
value.userProfile.connectionType,
value.userProfile.dateOfBirth,
this.currentPage,
)
.then(newProfiles => {
this.setState(
{
profiles: [...this.state.profiles, ...newProfiles],
},
() => {
this.currentPage = this.currentPage + 1;
},
);
})
.catch();
}
}
public componentDidMount() {
// tslint:disable-next-line: no-console
console.log('Calling loadProfiles');
this.loadProfiles();
}
public render() {
return (
<Grid container justify="center" direction="column" alignContent="center">
<Paper>
<Grid container item spacing={40} style={{ maxWidth: '840px' }}>
{this.state.profiles.map(profile => (
<Grid
key={profile.userId}
item
sm={6}
style={{ maxWidth: '200px' }}
>
<Link
to={`/profile/${profile.userId}`}
style={{ textDecoration: 'none' }}
>
<ProfileCard
key={profile.userId}
name={profile.name}
picUrl={profile.picUrl}
userId={profile.userId}
age={profile.age}
orientation="horizontal"
location={profile.location}
/>
</Link>
</Grid>
))}
</Grid>
</Paper>
</Grid>
);
}
}
const authCondition = (authUser: any) => !!authUser;
export const Home = withAuthorization(authCondition)(HomeComponent);
Also, my react and react-dom versions are both 16.8.6.
After a lot of research, it appears that the proper way to handle this is to actually add methods within the Context object that modifies the state within the App (or whichever component houses the Context.Provider object.