How to fetch API at component level and prerender it in nextjs? - reactjs

We need to populate the list of links in the footer component But these methods getInitialProps, getServerSideProps and getStaticProps are not getting executed in the footer component. Here is the code:
const Layout = (props) => {
return (
<>
<Header />
{props.children}
<Footer />
</>
);
}
export default Layout;
--- Footer Component ---
const Footer = ({data}) => {
return(
<ul>
{data && data.map((todo,index)=>{
return <li key={index}>
{todo.title}
</li>
})}
</ul>
);
}
export async function getStaticProps() { // not working with getInitialProps and getServerSideProps as well
const res = await fetch('https://jsonplaceholder.typicode.com/todos')
const data = await res.json();
return {
props: {data}
}
}
export default Footer;
EDIT: Footer component should not displayed in Login/Signup pages.
In the _app.js, i have passed the data as <Component {...pageProps} footerData={...data} /> but the data object is not available in Footer component.

getInitialProps, getServerSideProps, and getStaticProps just only run on top of the page, not component.
If you want to run fetch on Footer, you can call fetch function on useEffect hook, or simply call fetch in one of the getInitialProps, getServerSideProps, and getStaticProps on top of the page (i.e such as _index.js) and pass down Footer by props.
You also can use getInitialsProps on top application in _app.js;
import App, {Container} from 'next/app'
import React from 'react'
import Footer from 'somewhere/Footer';
export default class MyApp extends App {
static async getInitialProps ({ Component, router, ctx }) {
let pageProps = {}
let data = await fetch('/api');
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return {data, pageProps}
}
render () {
const {Component, pageProps, data} = this.props
return <Container>
<Component {...pageProps} footerData={data} />
</Container>
}
}
and FooterComponent look like this
export const Footer = ({data}) => {
return(
<ul>
{data && data.map((todo,index)=>{
return <li key={index}>
{todo.title}
</li>
})}
</ul>
);
}
on the pages except Login, Signup which need Footer component (Home/index.tsx, User/index.tsx)
export const Home = ({footerData}) = {
return <div>
<Header/>
<Footer data={footerData} />
</div>
}

Related

How to make <li> switch to another page on click in react.js

I have to fetch data from an API and display it as a list, and then when you click on an item in the list it should take you to another page with details about that item. I don't know how to make the li element take me to another page with details
This is my app.js
import React, { useState, useEffect } from "react";
import CharacterList from "./Components/CharacterList";
import Navbar from "./Components/Navbar";
function App() {
const [characters, setCharacters] = useState([]);
const callBreakingBadAPI = async () => {
const url = `https://www.breakingbadapi.com/api/characters?name`;
const resp = await fetch(url);
const data = await resp.json();
setCharacters(data);
};
useEffect(() => {
callBreakingBadAPI();
}, []);
return (
<div className="container">
<Navbar />
{<CharacterList characters={characters} />}
</div>
);
}
export default App;
this is characterList.js
import React from "react";
import CharacterListItem from "./CharacterListItem";
const CharacterList = ({ characters }) => {
return (
<section>
{characters.map((character) => (
<CharacterListItem character={character} key={character.char_id} />
))}
</section>
);
};
export default CharacterList;
And this is my CharacterListItem.JS
import React from "react";
const CharacterListItem = ({ character }) => {
return (
<ul>
<li>Name: {character.name}</li>
</ul>
);
};
export default CharacterListItem;
I have a characterinfo folder inside my components folder with characterInfo.js where I want to switch to show details about the clicked item.
You can add a onClick handler to your li
const history = useHistory()
<li onClick={() => history.push(/* your url */)}>
Name: {character.name}
</li>
It is recommended not to add onClick listeners to non-interactable html elements for accessibility. If you still want to add the listener, you will have to look at how to make the element accessible. For example, adding tabindex="1" for keyboard users, etc.
Best element type for this usecase would be link (or in some cases, button)
<li>
<Link to={/* your url */}>
Name: {character.name}
</Link>
</li>
You can send the character details from CharacterListItem.js through Link state.
import { Link } from "react-router-dom";
const CharacterList = ({ character }) => {
return (
<Link to="/character-info" state={{data: character}} >Name: {character.name}</Link>
);
};
and from charactorInfo.js, data can be recieved using useLocation hook.
import { useLocation } from "react-router-dom";
const CharacterInfo = () => {
const location = useLocation();
const character = location.state?.data;
console.log(character);
return (
<div>
{/* Display character here */}
</div>)
}

Can a single product detail page appear on the same page as the product list instead?

I'm tasked with using react to create our online store. So far I've succesfully called our products using the data from the API we're developing, and I've also been able to pass the data from the mapped product list to a single product page using a link.
Only issue now is that we'd like the single product to appear on the same page as the product list when it's clicked on by the user, perhaps as a component that appears above the product list (as opposed to linking to a separate page). For the life of me I cannot seem to find a method of doing this that doesn't result in an error or the app reading parameters as undefined. Admitedly, I am quite new to React, so it's possible I'm missing something very obvious.
This is the ProductList.js
import React, { useState, useEffect } from 'react';
import SingleProduct from './SingleProduct';
import { Link } from 'react-router-dom';
const API_URL = "http://exampleapiurl/ExampleCollection/Examplecollectionid";
const Products = () => {
const [products, setProducts] = useState([]);
useEffect(() => {
getProducts().then((products) => setProducts(products))
}, []);
const getProducts = () =>
fetch(API_URL)
.then((res) => res.json());
// OnClick Handler
const [isShown, setIsShown] = useState(false);
const handleClick = (e) => {
setIsShown(current => !current);
};
return (
<div className="GetProducts">
<h1> Fetch Products from a Collection </h1>
<div className="container">
<div className="row">
{/* 👇️ Ideally, we'd like the single product item to appear here on button click, as opposed to a separate page */}
{
isShown &&
<SingleProduct/>
}
{products.map((frame) => (
<div>
{/* 👇️ Current link to separate page for product*/}
<Link to={`/SingleProduct/${frame.frameId}`}>
{/* 👇️ Button to show single item on same page as product list.*/}
<button onClick={handleClick} value={frame.frameId} > View {frame.frameName}</button>
<div key={frame.frameId}>
<img src={`https://exampleimageurl/${frame.thumnail}`} />
<li>Frame Name: {frame.frameName}</li>
<li>Gender: {frame.gender}</li>
</div>
</Link>
</div>
))
}
</div>
</div>
</div>
)
}
export default Products;
This is the SingleProduct.js
class SingleProduct extends React.Component {
constructor(props) {
super(props)
this.state = {
isLoading: false,
item: [],
frameId: this.props.match.params.frameId
}
}
componentDidMount() {
this.setState({ isLoading: true });
fetch(`http://exampleapiurl/${this.state.frameId}`)
.then(response => response.json())
.then(json => {
this.setState({
item: json,
isLoading: false
})
})
}
render() {
const { item } = this.state;
return (
this.state.isLoading ?
(<h1>Loading {this.state.frameId}...</h1>)
:
(
<div>
<div className="col border text-center" key={item.frameId}>
<img src={`https://exampleimageurl/${item.framePic}`} />
<li>Frame Name: {item.frameName}</li>
<li>Gender: {item.gender}</li>
</div>
</div>
)
)
}
}
export default SingleProduct
App.js
import React, { Component } from 'react';
import { Route } from 'react-router';
import { Home } from './components/Home';
import { Layout } from './components/Layout';
import Products from './components/ProductList';
import SingleProduct from './components/SingleProduct';
export default class App extends Component {
static displayName = App.name;
render() {
return (
<Layout>
<Route exact path='/' component={Home} />
<Route path='/ProductList' component={Products} />
<Route path='/SingleProduct/:frameId' component={SingleProduct} />
</Layout>
);
}
}
So if I understand correctly you don't want to use route for passing the data instead of that you can then pass props to the SingleProduct component.
With props getting passed it should look
{
isShown &&
<SingleProduct frameId = {selectedFrameId}/>
}
Declare a new state variable to store the selected frameid
const [selectedFrameId, setSelectedFrameId] = useState<Number>();
Your onclick event will need adjustment, because you will need to pass the frameid in map function.
onClick={() => this.handleClick(frame.frameId)}
and then set the state via handleClick event
const handleClick = (frameId) => {
setIsShown(current => !current);
setSelectedFrameId(frameId);
};
With this in your SingleProduct component the fetch call can directly use the props(frameid)
fetch(`http://exampleapiurl/${this.props.frameId}`)
Also I would recommend to change SingleProduct to a functional component instead of class component.

Passing down Props to "Single Page Views" through components

Hey still new to React but I'm grinding my way through it slowly by building my own personal app/platform. I have a quick question of passing down props to single page views. This is my overview page that will pull in all the teams from my database as such:
import React, { useState, useEffect } from 'react';
import firebase from '../../firebase/firebase.utils'
import Button from '../../Components/GeneralComponents/Button.component'
import * as GoIcons from 'react-icons/go';
import TeamList from '../../Components/Teams/TeamList.Component'
function TeamsPage() {
const [teams, setTeams] = useState([]);
const [loading, setLoading] = useState(false);
const ref = firebase.firestore().collection("teams");
function getTeams() {
setLoading(true);
ref.onSnapshot((querySnapshot) => {
const items = [];
querySnapshot.forEach((doc) => {
items.push(doc.data());
});
setTeams(items);
setLoading(false);
console.log(items);
});
}
useEffect(() => {
getTeams();
},[])
if(loading) {
return <h1>Loading...</h1>
}
return (
<div className="content-container">
<h2>Team Page</h2>
<div className="add-section">
<div className="actions">
<Button
className="bd-btn outlined add-team"
><GoIcons.GoGear/>
Add Team
</Button>
</div>
</div>
<TeamList teams={teams} />
</div>
)
}
export default TeamsPage;
This gets passed into my TeamList Component:
import React from 'react';
import { Link } from 'react-router-dom'
import { TeamCard } from './TeamCard.Component';
const TeamList = props => {
return(
<div className='teams-overview'>
{props.teams.map(team => (
<Link to={`/teams/${team.id}`}>
<TeamCard key={team.id} team={team}/>
</Link>
))}
</div>
)
}
export default TeamList;
Which maps through and then list the Team as a card component with a link that is supposed to route to their id and pass through their data.
Now in my single page view of a team I'm struggling to gain access to that prop data:
import React from 'react'
function TeamSinglePage(team) {
return (
<div className="content-container">
<h1>Single Page View</h1>
<p>Welcome, {team.teamName}</p>
</div>
)
}
export default TeamSinglePage;

React routing - id params in the URL is undefined when I pass it with history.push

I managed to to use history.push in an onClick as I want to pass the user id to a Profile page component but the uuid params in the URL is undefined and I don't know why. I'm really stuck at this part.
I also want to pass all the other props which I get from the Random User Generator API as I'm doing in CardList to be able to build the profile page.
Would definitely appreciate anyone’s help.
import React, { Fragment } from "react";
import { withRouter } from "react-router-dom";
const Card = ({ history, firstName, lastName, email, uuid, image, city, country }) => {
return (
<Fragment>
<div className="tc bg-washed-green dib br3 pa3 ma2 dim bw2 shadow-5 pointer">
<img src={image} alt="userImage" onClick={() => history.push(`/profilepage/${uuid}`)} />
<h2>{`${firstName} ${lastName}`}</h2>
<p> {email} </p>
<div>
<span>{`${city}, ${country}`}</span>
</div>
</div>
</Fragment>
);
};
export default withRouter(Card);
import React, { Fragment } from "react";
const ProfilePage = ({ uuid }) => {
return (
<Fragment>
<h1 className="f1">Profile Page: {uuid}</h1>
</Fragment>
);
};
export default ProfilePage;
and this is the Routing in App.js
render() {
const { users, isPending } = this.props;
if (isPending) {
return <h1 className="tc"> Loading... </h1>;
} else {
return (
<div className="tc">
<Switch>
<Route exact path="/homepage" render={() => <CardList users={users} />} />
<Route path="/profilepage/:uuid" component={ProfilePage} />
</Switch>
</div>
);
}
}
}
import React, { Fragment } from "react";
import Card from "./Card";
const CardList = ({ users }) => {
return (
<Fragment>
<h1 className="f1"> IOTA Users </h1>
{users.map((user) => {
return (
<Card
key={user.login.uuid}
image={user.picture.large}
firstName={user.name.first}
lastName={user.name.last}
email={user.email}
city={user.location.city}
country={user.location.country}
/>
);
})}
</Fragment>
);
};
export default CardList;
In your ProfilePage component you can get the uuid like below ways
Approach-1: In this approach you either need to spread all the other props which will be sent from parent or else need to use ...rest param to capture all the other props which you don't want to spread.
import React, { Fragment } from "react";
const ProfilePage = ({ match }) => {
return (
<Fragment>
<h1 className="f1">Profile Page: {match.params.uuid}</h1>
</Fragment>
);
};
export default ProfilePage;
Approach-2: This way you can access other props also
import React, { Fragment } from "react";
const ProfilePage = (props) => {
return (
<Fragment>
<h1 className="f1">Profile Page: {props.match.params.uuid}</h1>
</Fragment>
);
};
export default ProfilePage;
EDIT: Look like you don't send uuid as props. Can you check CardList component?
There is undefined because it's not actually send uuid data as props. You should fetch it from this.props.match.params.uuid
Can you also check is there exist your id in url? If so my method should work.
And from react-router-dom version 5 you can use their hook like useParams. So, you can make your code base more clear

Not able to access detail information from Api using React Router not rendering on the page

I am building a small React Routing application to get a better idea as to how it work. My App.js looks like this with the basic routing:
import React from 'react';
import './App.css';
import Nav from './Nav';
import About from './About';
import Shop from './Shop';
import CountryDetail from './CountryDetail'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
function App() {
return (
<Router>
<div className="App">
<Nav />
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/shop" exact component={Shop} />
<Route path="/shop/:name" component={CountryDetail} />
</Switch>
</div>
</Router>
);
}
const Home = () => (
<div>
<h1>Home Page</h1>
</div>
);
Now the Shop component a list of countries from the api which is in the code below:
import React from 'react';
import './App.css';
import { useEffect } from 'react';
import { useState } from 'react';
import {Link} from 'react-router-dom';
function Shop() {
useEffect(() => {
fetchItems();
},[])
const [countries, setCountries] = useState([])
const fetchItems = async () => {
const data = await fetch('https://restcountries.eu/rest/v2/all');
const countries = await data.json();
console.log(countries);
setCountries(countries);
}
return (
<div>
{countries.map(country => (
<div>
<Link to={`shop/${country.name}`}>
<h1 key={country.alpha2Code}>
{country.name}
</h1>
</Link>
<p>Popluation {country.population}</p>
<p> Region {country.region}</p>
<p>Capital {country.capital}</p>
</div>
)
)}
</div>
);
}
export default Shop;
Now what I want to do is render more information about the country when I click on it. So I have created another component called CountryDetail:
import React from 'react';
import './App.css';
import { useEffect } from 'react';
import { useState } from 'react';
function CountryDetail( { match } ) {
useEffect(() => {
fetchItem();
console.log(match)
},[])
const [country, setCountry] = useState([])
const fetchItem = async ()=> {
const fetchCountry = await fetch(`https://restcountries.eu/rest/v2/name/${match.params.name}`);
const country = await fetchCountry.json();
setCountry(country);
console.log(country);
}
return (
<div>
<h1>Name {country.name}</h1>
<p>Native Name{country.nativeName}</p>
<p>Region {country.region}</p>
<p>Languages {country.languages}</p>
<h1>This Country</h1>
</div>
);
}
export default CountryDetail;
The problem I am having is that it is not rendering anything on the CountryDetail page. I am sure I have hit the api correctly but not sure if I am getting the data correctly. Any help would be appreciated.
Issue: The returned JSON is an array but your JSX assumes it is an object.
Solution: You should extract the 0th element from the JSON array. Surround in a try/catch in case of error, and correctly render the response.
Note: the languages is also an array, so that needs to be mapped
function CountryDetail({ match }) {
useEffect(() => {
fetchItem();
console.log(match);
}, []);
const [country, setCountry] = useState(null);
const fetchItem = async () => {
try {
const fetchCountry = await fetch(
`https://restcountries.eu/rest/v2/name/${match.params.name}`
);
const country = await fetchCountry.json();
setCountry(country[0]);
console.log(country[0]);
} catch {
// leave state alone or set some error state, etc...
}
};
return (
country && (
<div>
<h1>Name {country.name}</h1>
<p>Native Name{country.nativeName}</p>
<p>Region {country.region}</p>
<p>Languages {country.languages.map(({ name }) => name).join(", ")}</p>
<h1>This Country</h1>
</div>
)
);
}
As you said it yourself, the response is an array (with a single country object in it), but you are using it as if it would be an object.
So, instead of:
const country = await fetchCountry.json();
setCountry(country);
It should be:
const countries = await fetchCountry.json();
setCountry(countries[0]);

Resources