Next.js, getStaticProps not working with component but does with page - javascript

If I visit this code on local host, it is able to pull data from the API and then display it on a card.
import { formatNumber, parseTimestampJM } from '../../utils';
import { Card } from './UserTransactions.styled';
// STEP 1 : fetch data from api
export async function getStaticProps() {
const res = await fetch(
'https://proton.api.atomicassets.io/atomicmarket/v1/sales'
);
const data = await res.json();
return {
props: {
data,
},
};
}
function UserTransactionsComponent({ data }) {
const results = data;
console.log(results);
return (
<PageLayout>
<div>
<h1>This is a list of User Transactions!</h1>
</div>
<ul>
{results.data.map((result) => {
const {
sale_id,
buyer,
seller,
listing_price,
listing_symbol,
created_at_time,
} = result;
if (buyer !== null) {
return (
<Card>
<li key={sale_id}>
<h3>
{seller} just sold item number {sale_id} to {buyer} for{' '}
{formatNumber(listing_price)} {listing_symbol} at{' '}
{parseTimestampJM(created_at_time)}
</h3>
</li>
</Card>
);
}
})}
</ul>
</PageLayout>
);
}
export default UserTransactionsComponent;
When I create a component and then call it in to my index page like so:
<PageLayout>
<Banner modalType={MODAL_TYPES.CLAIM} />
<ExploreCard />
<HomepageStatistics />
<Title>New & Noteworthy</Title>
<UserTransactionsComponent />
<Grid items={featuredTemplates} />
</PageLayout>
);
};
export default MarketPlace;
it gives me the following error
TypeError: Cannot read properties of undefined (reading 'data')
27 | <ul>
> 28 | {results.data.map((result) => {
| ^
29 | const {
30 | sale_id,
31 | buyer,
I think that the reason I'm getting this error is because of the way the data is being fetched. Perhaps it's not being included in the component.

getStaticProps works only for page components inside pages folder. And the data is fetched at build time. If you wanna use UserTransactionsComponent as a normal component, you should use useEffect and make the API call on mount.
Here is what Next.js's documentation says about getStaticProps:
If you export a function called getStaticProps (Static Site Generation) from a page, Next.js will pre-render this page at build time using the props returned by getStaticProps.
Here is UserTransactionsComponent as a normal component:
import {useState, useEffect} from "react"
function UserTransactionsComponent() {
const [data, setData]=useState();
useEffect(()=>{
async function fetchData() {
const res = await fetch(
'https://proton.api.atomicassets.io/atomicmarket/v1/sales'
);
const {data} = await res.json();
setData(data)
}
fetchData()
},[]);
if(!data){
return (<div>Loading...</div>)
}
return (
<PageLayout>
<div>
<h1>This is a list of User Transactions!</h1>
</div>
<ul>
{data.map((result) => {
const {
sale_id,
buyer,
seller,
listing_price,
listing_symbol,
created_at_time,
} = result;
if (buyer !== null) {
return (
<Card>
<li key={sale_id}>
<h3>
{seller} just sold item number {sale_id} to {buyer} for{' '}
{formatNumber(listing_price)} {listing_symbol} at{' '}
{parseTimestampJM(created_at_time)}
</h3>
</li>
</Card>
);
}
})}
</ul>
</PageLayout>
);
}
export default UserTransactionsComponent;

Related

{Answered} React Shows Data in console but does not render it on page

I am super new to React and have just started my journey with it I have another page where I use the same function of handleIndexEmployees and the same const variables with useState and useEffect which load just fine. Right now with the code I have the only thing that shows up is
<h1>Employees</h1>
import { useState, useEffect } from "react";
import axios from "axios";
import { EmployeesAxios } from "./EmployeesAxios";
export function EmployeesIndex() {
const [employees, setEmployees] = useState([]);
const handleIndexEmployees = () => {
console.log("Wait I'm getting Da Minions");
axios.get("http://localhost:3000/employees.json").then((response) => {
console.log("Hello my Minions");
console.log(response.data);
setEmployees(response.data);
});
};
useEffect(handleIndexEmployees, []);
return (
<div>
<EmployeesAxios employee={employees} />
</div>
);
}
export function EmployeesAxios(props) {
return (
<div>
<h1>Employees</h1>
{props.employee.map((employee) => {
<div key={employee.id}>
<h3>Name: {` ${employee.first_name} ${employee.last_name}`}</h3>
</div>;
})}
</div>
);
}
{props.todos.map((todo) => (
<div key={todo.id}>
<h2>{todo.name}</h2>
<p>{todo.description}</p>
<button
onClick={() => {
axios.patch(`http://localhost:3000/todos/${todo.id}.json`).then((response) => {
console.log(response);
});
}}
>
{todo.done}
Done
</button>
<button onClick={() => props.onShowTodo(todo)}>More info</button>
</div>
))}
I tried putting this all on one page with making all the requests on that page, and I get the same response with that so my page that I am rendering for a selector wheel to assign tasks to that employee works just fine with using props so that is why I tried moving all the axios requests to another page and passing in props. I also tried using isLoading as well and this didn't work either.
It looks like you are not returning anything from your map.
Add a return statement here
{
props.employee.map((employee) => {
// Return something from the map
return (
<div key={employee.id}>
<h3>Name: {` ${employee.first_name} ${employee.last_name}`}</h3>
</div>
);
});
}

How to use the IF statement to avoid the Weather API fetch result crashing once the user types wrong the city name?

Summarizing The Problem
- Details About The Goal
A weather app that renders on the screen the data fetched from OpenWeather API.
- Actual and Expected Results
Whether the user types correctly or not the city name or presses enter in the empty field, no result render on the screen. I would like help to resolve it.
What it Has Been Tried So Far (Update 1.1)
I've placed a conditional operator below the *Search* component in the *App.js* file:
{typeof dataSearch === "undefined" ? (<></>) : ()}
{typeof dataSearch === "undefined" ? (
<></>
) : (
<>
<CurrentWeather resultData={weatherData} />
<ForecastWeather resultData={forecastData} />
</>
)}
I would not expect that it would leave the screen blank.
Well, thank you for reading my post.
The Complete Code
- App.js (Update 1.1)
import React, { useState } from "react";
import { Api } from "./Api";
import { Container } from "react-bootstrap";
import {
Search,
CurrentWeather,
ForecastWeather,
Footer,
} from "./components/index";
import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";
function App() {
const [weatherData, setWeatherData] = useState(null);
const [forecastData, setForecastData] = useState(null);
const handleSearchLocation = (dataSearch) => {
const weatherDataFetch = fetch(
`${Api.url}/weather?q=${dataSearch}&units=metric&appid=${Api.key}`
);
const forecastDataFetch = fetch(
`${Api.url}/forecast?q=${dataSearch}&units=metric&appid=${Api.key}`
);
Promise.all([weatherDataFetch, forecastDataFetch]).then(
async (response) => {
const weatherResponse = await response[0].json();
const forecastResponse = await response[1].json();
setWeatherData(weatherResponse);
setForecastData(forecastResponse);
}
);
};
return (
<div className="App">
<div className="contentApp">
<Container>
<Search
searchResultData={handleSearchLocation}
textPlaceholder="Search for a place..."
/>
{typeof dataSearch === "undefined" ? (<></>) : (
<>
<CurrentWeather resultData={weatherData} />
<ForecastWeather resultData={forecastData} />
</>
)}
<Footer />
</Container>
</div>
</div>
);
}
export default App;
Yippee-ki-yay
Summarizing The Problem
The main issue that I was facing was with handling the API logic to display the search result on the screen and before that, once the user types wrong the city name or pressed enter into the blank input field, the program crashed.
However, I've begun to search for why this was happening and in the course of time after observing other codes, I've seen that the IF statement should be used to fix this issue.
Problem Resolution
After several attempts, the solution was to remove the Promise.all() and separate them, weather and forecast, into await blocks of codes with their own IF statement:
// App.js
await weatherDataFetch
.then((res) => {
if (!res.ok) {
throw new Error("City name: typed wrong or blank input.");
}
return res.json();
})
.then((res) => {
setWeatherData(res);
})
.catch((err) => {
console.log(err);
});
await forecastDataFetch
.then((res) => {
if (!res.ok) {
throw new Error(
"Weather forecast not found. Waiting for the correct city name."
);
}
return res.json();
})
.then((res) => {
setForecastData(res);
})
.catch((err) => {
console.log(err);
});
The async has been moved to the top of the handleSearchLocation function:
// App.js
const handleSearchLocation = async (dataSearch) => {
And for the last, deleting the old attempt to clean and avoid conflict and crashes:
// Old
// App.js
return (
<div className="App">
<div className="contentApp">
<Container>
<Search
searchResultData={handleSearchLocation}
textPlaceholder="Search for a place..."
/>
{typeof dataSearch === "undefined" ? (<></>) : (
<>
<CurrentWeather resultData={weatherData} />
<ForecastWeather resultData={forecastData} />
</>
)}
<Footer />
</Container>
</div>
</div>
);
// New
// App.js
return (
<div className="App">
<div className="contentApp">
<Container>
<Search
searchResultData={handleSearchLocation}
textPlaceholder="Search for a place..."
/>
{weatherData && <CurrentWeather resultData={weatherData} />}
{forecastData && <ForecastWeather resultData={forecastData} />}
<Footer />
</Container>
</div>
</div>
);
Note
Other beginners, like me, observe other codes and especially code variations of the same programs because there are a lot of ways to do the same thing.
Yippee-ki-yay

How do I use the output of one axios request as a dependency for another when rendering components in React?

I have been struggling with this for some time and I am not sure how to solve the issue.
Basically, I am trying to render some components onto my Index page, this is my code below:
App.js
import Index from "./Components/Index"
import axios from "axios"
export default function App() {
const [movieList, setMovieList] = React.useState([])
let featured = []
let coming = []
let showing = []
React.useEffect(() => {
console.log("Ran App Effects")
axios.get(`API_CALL_TO_GET_LIST_OF_MOVIES`)
.then(res =>{
setMovieList(res.data)
})
}, [])
return(
<div>
{movieList.map(movie =>{
if(movie.status === 'featured'){
featured.push(movie.api_ID)
} else if (movie.status === 'upcoming'){
coming.push(movie.api_ID)
} else{
showing.push(movie.api_ID)
}
})}
<Index featured={featured} coming={coming} showing={showing}/>
</div>
)
}
In the code above I am receiving an array of Objects and based on what is in their status I am putting them in some empty arrays and sending them as props into my Index component.
This is what my index component looks like:
import React from "react"
import Header from "./Header"
import Footer from "./Footer"
import MovieCard from "./MovieCard"
import axios from "axios"
export default function Index(props) {
const [featuredMovies, setFeaturedMovies] = React.useState([])
const [comingMovies, setComingMovies] = React.useState([])
//const featured = [419704,338762,495764,38700,454626,475557]
//const coming = [400160,514847,556678,508439,524047,572751]
React.useEffect(() => {
console.log("Ran Effect")
axios.all(props.featured.map(l => axios.get(`API_CALL_TO_GET_SPECIFIC_MOVIE/${l}`)))
.then(axios.spread(function (...res){
setFeaturedMovies(res)
}))
.catch((err) => console.log(err))
axios.all(props.coming.map(l => axios.get(`API_CALL_TO_GET_SPECIFIC_MOVIE/${l}`)))
.then(axios.spread(function (...res){
setComingMovies(res)
}))
.catch((err) => console.log(err))
}, [])
return(
<body>
<Header />
<section className="home">
<div className="container">
<div className="row">
<div className="col-12">
<a className="home__title">FEATURED MOVIES</a>
</div>
{ featuredMovies.map(movie =>{
return <MovieCard movie={movie} featured={true} />
}) }
{console.log(props.featured)}
</div>
</div>
</section>
<section className="home">
<div className="container">
<div className="row">
<div className="col-12">
<a className="home__title">COMING SOON</a>
</div>
{ comingMovies.map(movie =>{
return <MovieCard movie={movie} featured={false} />
})}
</div>
</div>
</section>
<Footer/>
</body>
)
}
The issue I am running into is, whenever I run the app for the first time it works fine but then when I hit the refresh button the components do not render anymore
The only time it re-renders when I refresh the page is when I uncomment,
//const featured = [419704,338762,495764,38700,454626,475557]
//const coming = [400160,514847,556678,508439,524047,572751]
and replace the props.featured.map and props.coming.map with featured.map and coming.map hence using the hard coded values and not the values passed in from the props.
Any help with this would be much appreciated as I am completely stuck and currently pulling my hair out.
I took the liberty to tinker with your code. In the example below I've rearranged the data into three sets with the help of useMemo and by checking the status property of each movie. It is important to keep any data related logic outside of the render logic.
I also moved around some of your HTML structure. You were outputting a <body> tag inside of a <div>. The outer layer should be in control of the outer HTML structure, so I moved that HTML to the App component.
import { useState, useEffect, useMemo } from 'react'
import Header from "./Components/Header"
import Footer from "./Components/Footer"
import Index from "./Components/Index"
import axios from "axios"
export default function App() {
const [movieList, setMovieList] = useState([])
const featuredMovies = useMemo(() => {
return movieList.filter(({ status }) => status === 'featured');
}, [movieList]);
const upcomingMovies = useMemo(() => {
return movieList.filter(({ status }) => status === 'upcoming');
}, [movieList]);
const showingMovies = useMemo(() => {
return movieList.filter(({ status }) => status !== 'featured' && status !== 'upcoming');
}, [movieList]);
useEffect(() => {
axios.get(`API_CALL_TO_GET_LIST_OF_MOVIES`)
.then(res =>{
setMovieList(res.data)
})
}, [])
return (
<body>
<Header />
<Index data={featuredMovies} title="Featured Movies" featured={true} />
<Index data={upcomingMovies} title="Coming Soon" />
<Index data={showingMovies} title="Showing Now" />
<Footer/>
</body>
)
}
Since we now have three sets of movies (featured, upcoming, and playing) it would also make sense to have three components that handle those data sets instead of having one that handles multiple. Each Index component gets its own data set and other props to render the movies within it.
import MovieCard from "./MovieCard"
export default function Index({ data, title, featured = false }) {
return (
<section className="home">
<div className="container">
<div className="row">
<div className="col-12">
<a className="home__title">{title}</a>
</div>
{data.map(movie => {
return <MovieCard movie={movie} featured={featured} />
})}
</div>
</div>
</section>
);
}

Dynamic routing with getServerSideProps in Nextjs

I'm trying to learn nextjs. Struggling to work out routing with getServerSideProps.
Using a free API I have a list of countries displayed on the DOM. I want to dynamically link to a country and data be fetched and displayed for that specific country.
Heres my code so far
const Country = props => (
<Layout>
<h1>{props.country.name}</h1>
<span>{props.country.capital}</span>
</Layout>
);
export async function getServerSideProps(context) {
const { id } = context.query;
const res = await fetch(`https://restcountries.eu/rest/v2/name/${id}`);
const country = await res.json();
console.log(`Fetched place: ${country.name}`);
return { props: { country } };
}
export default Country;
<div className='container'>
<Head>
<title>Countries List</title>
<link rel='icon' href='/favicon.ico' />
</Head>
<Layout>
<main>
<h1>
Countries{' '}
<span role='img' aria-label='world emoji'>
🌎
</span>
</h1>
<ul>
{countries.map(country => (
<li key={country.name}>
<Link href='/p/[id]' as={`/p/${country.name}`}>
<a>{country.name}</a>
</Link>
</li>
))}
</ul>
</main>
</Layout>
</div>
);
export async function getServerSideProps() {
// Call an external API endpoint to get posts.
const res = await fetch('https://restcountries.eu/rest/v2/all');
const countries = await res.json();
// By returning { props: posts }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
countries,
},
};
}
export default Home;
The URL dynamically routes ok. For example, when you click on Afghanistan the URL shows http://localhost:3000/p/Afghanistan.
My country component however doesn't display anything and undefined is printed to the terminal.
Example of URL and response from URL: https://restcountries.eu/rest/v2/name/Afghanistan
{
name: "Afghanistan"
}
Apologies if a noob question. Trying to learn nextjs
export async function getServerSideProps(context) {
const { id } = context.query;
const res = await fetch(`https://restcountries.eu/rest/v2/name/${id}`);
const country = await res.json();
console.log(`Fetched place: ${country.name}`);
return { props: { country } };
}
you are returning a nested object from above function
{ props: { country:country } }
so this prop will be attached to props as like this:
`props.props`
this is how you should implement
const Country = props => (
<Layout>
<h1>{props.props.country.name}</h1>
<span>{props.props.country.capital}</span>
</Layout>
);
UPDATE
In early version of next.js I think updated after version 9, we were not returning from serverside function by using props. As of now correct way of implementation is
return {
props: {
countries,
},
};
Next.js 13 Update
In next.js 13, if you set app directory, components in this directory will be server-rendered components by default. That means everything will be run on the server and we do not need to write specifiacallygetServerSideProps. in "app" directory, if your file name is surrounded by [..id], it means it is a dynamic route. In page.jsx, you can access id like this
export default function ProductPage({ params }) {
return (
<div>
<h1>Product ID: {params.id}</h1>
</div>
);
}
There's nothing wrong in how you're handling the dynamic routing of the page. The issue is that the data returned by the API is an array but your code expects it to be an object. You can retrieve the first item from the array and pass that to the component from getServerSideProps.
export async function getServerSideProps(context) {
const { id } = context.params; // Use `context.params` to get dynamic params
const res = await fetch(`https://restcountries.com/v2/name/${id}`); // Using `restcountries.com` as `restcountries.eu` is no longer accessible
const countryList = await res.json();
const [country] = countryList; // Get first item in array returned from API
return { props: { country } };
}
const Country = ({ country }) => {
console.log(country);
return (
<>
<h1>{country.name}</h1>
<span>{country.capital}</span>
</>
);
};
export default Country;
Just to add to the accepted answer, you could also destructure to make it (imho) more readable. This is entirely optional though
const Country = ({ country }) => (
<Layout>
<h1>{country.name}</h1>
<span>{country.capital}</span>
</Layout>
);

React won't render multiple fetched API's to the DOM

Link to CodeSandbox.
I can successfully display Tavares' fetched data to the DOM through a .map statement. However, once I try to load up the second and third players data using the exact same way (they're there, just commented out right now) - under Tavares' .map - I get thrown an error of, "Cannot read property 'map' of undefined", and the first .map (Matthews) after Tavares'.
Trying to figure out why this error is displaying, and why I can't map all three data points to the DOM.
App.JS
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import axios from "axios";
import "./styles.css";
function App() {
// Set initial state for data
const [data, setData] = useState({ tavares: [], matthews: [], marner: [] });
// Fetch data
useEffect(() => {
const fetchData = async () => {
// Grab all players API's
let tavares =
"https://statsapi.web.nhl.com/api/v1/people/8475166?expand=person.stats&stats=yearByYear,careerRegularSeason&expand=stats.team&site=en_nhlCA";
let matthews =
"https://statsapi.web.nhl.com/api/v1/people/8479318?expand=person.stats&stats=yearByYear,careerRegularSeason&expand=stats.team&site=en_nhlCA";
let marner =
"https://statsapi.web.nhl.com/api/v1/people/8478483?expand=person.stats&stats=yearByYear,careerRegularSeason&expand=stats.team&site=en_nhlCA";
// Axios to get all api's
axios
.all([axios.get(tavares), axios.get(matthews), axios.get(marner)])
.then(
axios.spread((tavares, matthews, marner) => {
setData(
{ tavares: [tavares.data.people[0]] },
{ matthews: [matthews.data.people[0]] },
{ marner: [marner.data.people[0]] }
);
console.log("Tavares:", tavares.data.people[0]);
console.log("Matthews:", matthews.data.people[0]);
console.log("Marner:", marner.data.people[0]);
})
);
};
fetchData();
}, []);
return (
<>
<h1>Tavares</h1>
<ul>
{data.tavares.map(item => (
<li key={item.objectID}>
<p>{item.id}</p>
<p>{item.primaryNumber}</p>
</li>
))}
</ul>
{/* <h1>Matthews</h1>
<ul>
{data.matthews.map(item => (
<li key={item.objectID}>
<p>{item.id}</p>
<p>{item.primaryNumber}</p>
</li>
))}
</ul>
<h1>Marner</h1>
<ul>
{data.marner.map(item => (
<li key={item.objectID}>
<p>{item.id}</p>
<p>{item.primaryNumber}</p>
</li>
))}
</ul> */}
</>
);
}
export default App;
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You are calling setData with three arguments (three objects with one player each) rather than an object with all players. This means only the first object with only tavares is getting set as your data (try logging your data object before rendering to see).
You are doing:
setData(
{ tavares: [tavares.data.people[0]] },
{ matthews: [matthews.data.people[0]] },
{ marner: [marner.data.people[0]] }
);
When you should be doing:
setData({
tavares: [tavares.data.people[0],
matthews: [matthews.data.people[0]],
marner: [marner.data.people[0]]
});

Categories