I'm trying to create dynamic headers using state in NextJs, my values however are blank when I view the page source. I suspect my state is not being resolved before the render method fires. Why would the rest of my content load in the render method but not the head ? Example code:
import Head from 'next/head'
myContent() {
const {name} = this.state
return (
<h1>{name}</h1>
)
}
render() {
const {myDescription} = this.state
return (
<>
<Head>
<meta name='description' content={myDescription} />
</Head>
{this.myContent()}
</>
)
}
export async function getServerSideProps() {
const fetchMyData = await fetch('https://myapi.com')
const fetchedMyData = await fetchMyData.json()
return {
props: {
data: fetchedMyData.entries,
}
}
}
I've also tried moving the head into the myContent() method. Same issue.
So turns out I was attempting to pass client side data to the server. I needed to utilise the context param then retrieve from props.
componentDidMount() {
console.log(this.props)
}
export async function getServerSideProps(context) {
const { params } = context
const fetchMyData = await fetch('https://myapi.com')
const fetchedMyData = await fetchMyData.json()
return {
props: {
data: fetchedMyData.entries,
params,
}
}
}
Related
The screenshot below is the problem.
it looks like the get getStaticProps is running twice when there is no data to context params and when there is already data in the context params.
Is there a way to not run wpgraphql query when context params are empty?
below is my file structure
[productSlug].js
import Layout from '#gb-components/layout'
import Product from '#gb-components/product/Product'
import { ProductContextProvider } from '#gb-contexts/ProductContext'
import { getDeliveryInformation } from '#gb-utils/queries/page'
import { getProductPageDataBySlug, getProductReviews, getProductAddOnsByProductId } from '#gb-utils/queries/product'
import { useRouter } from 'next/router'
export async function getStaticPaths() {
return {
paths: [],
fallback: true,
}
}
export async function getStaticProps(context) {
const { categorySlug, productSlug } = context.params
console.log('[productSlug].js getStaticProps', productSlug)
// Fetch product data from wp site
const { data } = await getProductPageDataBySlug(productSlug)
// Fetch the product reviews base on sku in reviews.io api
const reviews = await getProductReviews(data?.product.sku)
// Fetch addons data base on product id
const addons = await getProductAddOnsByProductId(data?.product.productId)
const deliveryInformation = await getDeliveryInformation()
return {
props: { product: data?.product ? { ...data.product, ...reviews.data, ...addons?.data, ...deliveryInformation?.data } : [] }
}
}
export default function ProductPage(props) {
const router = useRouter()
const { product } = props
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (router.isFallback) {
return <Layout>
<div className='container'>
Loading....
</div>
</Layout>
}
return (
<Layout>
<ProductContextProvider price={product.price} id={product.productId}>
<Product product={product} />
</ProductContextProvider>
</Layout>
)
}
I tried adding a check if productSlug is equal to '[productSlug]' and it works but it feels like I shouldn't be doing this because I believe I am doing something wrong that's why [productSlug].js is triggering getStaticProps twice.
if ('[productSlug]' === productSlug) {
//Return nothing since there is nothing in the productSlug variable
return {
props: {}
}
}
export async function getStaticProps(context) {
const { categorySlug, productSlug } = context.params
console.log('[productSlug].js getStaticProps', productSlug)
// Fetch product data from wp site
const { data } = await getProductPageDataBySlug(productSlug)
// Fetch the product reviews base on sku in reviews.io api
const reviews = await getProductReviews(data?.product.sku)
// Fetch addons data base on product id
const addons = await getProductAddOnsByProductId(data?.product.productId)
const deliveryInformation = await getDeliveryInformation()
return {
props: { product: data?.product ? { ...data.product, ...reviews.data, ...addons?.data, ...deliveryInformation?.data } : [] },revalidate: 10,
}
}
its a major issue in static file regeneration , but next js give the solution already (ISR), need to add revalidate:10 , #here 10 is 10 seconds
https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration
I have an index page that includes data fetching within getServerSideProps.
If I use next/link or maybe router.push() - Is there a way for that data to persist across to the rest of the pages in my app?
Or is this a scenario where I'd need to use something like Context/Redux?
For example:
index.tsx
const App = ({ productData }: IndexProps) => {
return (
<Link href={`/product/${title}`}> ... </Link>
)
}
export const getServerSideProps: GetServerSideProps = async () => {
const productData = await getProducts();
return {
props: { productData },
};
};
/product/[id].tsx
const Product = ({ productData }) => {
return (
<Link href={`/product/${title}`}> ... </Link>
)
}
export const getServerSideProps: GetServerSideProps = async () => {
if (PRODUCTDATADOESNTEXIST) {
const productData = await getProducts();
}
// else use data fetched in from previous page?
return {
props: { productData },
};
};
Thanks!
You can create a service that will cache the data in memory.
Something like this.
const cache = {};
export const setProducts = (products) => {
products.forEach((p) => {
cache[p.id] = p;
});
};
const getProduct = (id) {
if(cache[id]){
Promise.resolve(cache[id]);
}
// make the API call to fetch data;
}
export default getProduct;
Use the set method to store the data from the NextJS Page and use the get method to fetch data when needed.
I'm new to next.js.
I'm rendering my page view on the backend side:
module.exports = (server, app) => {
server.get('/setup', (req, res) => {
const testData = {
name: "Test",
testProp: "testVal",
};
return app.render(req, res, '/setup', testData);
});
};
When I'm refreshing the page on my /setup route in the browser I can see the data inside getInitialProps, this is fine. But when I'm navigating from the other route to /setup route I can't see the data. Why this is happening and how can I change it in order to see the data? Here's my page code:
import { withAuth } from '../utils/withAuth';
const Setup = props => {
console.log(props); // when linking there is no expected data here
return(
<div>Setup here</div>
)
};
Setup.getInitialProps = async ctx => {
return { ...ctx.query };
};
export default withAuth(Setup);
When navigating from other routes, you are not rendering server-side, so you have to fetch the data from the server in getInitialProps.
import fetch from 'isomorphic-unfetch'
const Setup = (props) => {
console.log(props);
return(
<div>Setup here</div>
)
}
Setup.getInitialProps = async (ctx) => {
if (ctx.req) {
// server-side
return { ...ctx.query }
} else {
// client-side
const res = await fetch('API/setup')
const json = await res.json()
return { ...json }
}
}
I'd like to change what a component shows depending on the URL parameter but at the same time to use the same component. When I execute my code componentDidMount is evoked for the first time, but after the second time, it doesn't work. As a result, I can't change what the component shows.
I'm using react.js.
Although I used componentDidUpdate instead of componentDidMount it caused an infinitive loop.
import React from "react";
import ReactMarkdown from "react-markdown";
import Contentful from "./Contentful";
import "./Article.css";
class ArticlesWithTag extends React.Component {
state = {
articleFromContentful: []
};
async componentDidMount() {
console.log("componentDidMount");
const { tag } = this.props.match.params;
//get article from contentful API
const contentful = new Contentful();
try {
const article = await contentful.getArtcleWithTags(
undefined,
"blogPost",
tag
);
this.setState({ articleFromContentful: article.items });
} catch (err) {
console.log("error");
console.log(err);
}
}
render() {
const bodyOfArticle = this.state.articleFromContentful.map(data => {
const returnTitle = () => {
return data.fields.title;
};
const returnPublishedDate = () => {
let date = data.fields.publishDate.substring(0, 10);
let replacedDate = date.replace("-", "/");
while (date !== replacedDate) {
date = date.replace("-", "/");
replacedDate = replacedDate.replace("-", "/");
}
return replacedDate;
};
const returnBody = () => {
return data.fields.body;
};
const returnTags = () => {
const tagList = data.fields.tags.map(data => {
const listContent = `#${data}`;
return <li>{listContent}</li>;
});
return tagList;
};
returnTags();
return (
<div className="article-container">
<div className="article-title">{returnTitle()}</div>
<p className="article-date">{returnPublishedDate()}</p>
<div className="article-body">
<ReactMarkdown source={returnBody()}></ReactMarkdown>
</div>
<div className="article-tags">
<ul>{returnTags()}</ul>
</div>
</div>
);
});
return <div className="article-outer">{bodyOfArticle}</div>;
}
}
export default ArticlesWithTag;
Ok componentDidMount is a lifecylce method that runs only when the component mounts
and componentDidUpdate runs everytime there's an update
You can set state in componentDidUpdate but it must be wrapped around some condition
In your case you can do something like this
async componentDidMount() {
console.log("componentDidMount");
const { tag } = this.props.match.params;
//get article from contentful API
const contentful = new Contentful();
try {
const article = await contentful.getArtcleWithTags(
undefined,
"blogPost",
tag
);
this.setState({ articleFromContentful: article.items });
} catch (err) {
console.log("error");
console.log(err);
}
}
and for further updates use compnentDidUpdate as
async componentDidUpdate(prevProps) {
const { tag } = this.props.match.params;
//get article from contentful API
const contentful = new Contentful();
if ( tag !== prevProps.match.params ) // if props have changed
{
try {
const article = await contentful.getArtcleWithTags(
undefined,
"blogPost",
tag
);
this.setState({ articleFromContentful: article.items });
} catch (err) {
console.log("error");
console.log(err);
}
}
}
Hope it helps
componentDidMount is called only when your component is mounted.
componentDidUpdate is called when the props has changed.
You should create a function from your componentDidMount logic and call it in componentDidUpdate only when tag has changed
componentDidMount() is only called when the component initially mounts onto the DOM. Re-renders caused by prop or state changes trigger componentDidUpdate(). As you mentioned, making state changes in this function causes infinite loops.
What you can do is use componentDidUpdate() and check if props have changed before deciding to update the state again. This should prevent your infinite loop situation.
After many attempts I fail to use arrays from https://swapi.co/api/
What I want is to use data from people and films.
I have 2 files :
App.js
import React, { Component } from "react";
import List from './List';
const API = 'https://swapi.co/api/';
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
componentWillMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch(API);
const json = await response.json();
this.setState({
data: json.data
});
};
render() {
return (
<List data={this.state} />
);
}
}
List.js
import React, { Component } from 'react';
import Person from './Person';
class List extends Component {
render() {
const { data } = this.props;
const { results } = data;
return (
<div className="flex-grow-1">
<div className="row mb-5">{results}</div>
</div>
);
}
}
export default List;
So, how do I go through that array to get what data I want to display ? I'd like to render people -> results and films -> results
results would be undefined since results is not a node within your data object... try removing the line const {results} = data and in the return map the data array:
return (
<div className="flex-grow-1">
{
data.map((results, i) => {
return (<div key={i} className="row mb-5">{results}</div>);
})
}
</div>
);
you will need the key to avoid React's unique key warning
So this code as it is now will fetch the single object that acts as a map of other urls for entities in this API. what you want to do is modify fetchData so it accepts a url. At that moment you can do an initial request to /api, read the url from the result for people and films and call fetchData again with these urls. The received data can be saved inside the state.
A example for the implementation of componentWillMount() and fetchData():
componentWillMount() {
this.fetchData('https://swapi.co/api/')
.then(res => {
this.fetchData(res.people).then(people => alert(JSON.stringify(people)));
this.fetchData(res.films).then(people => alert(JSON.stringify(people)));
});
}
async fetchData(url) {
const response = await fetch(url);
return response.json();
};
The most important change is that fetchData now returns a promise. That allows us to use the then method to use the result of the first request. You can replace the alert with a setState implementation for yourself.