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 }
}
}
Related
I am working with vite and i am fetch a data from other node js server, and use getStaticProps to get a props and pass it to function component and when I console.log props it returned undefined. Here is a code:
function Home({books}) {
console.log(books)
return <div>Home</div>
}
export default Home
export const getStaticProps = async () => {
let books = await getAllBooks();
return ({
props: {
books
}
}
)
}
export const getAllBooks = async() => {
const res = await axios.get("http://localhost:3000/api/v1/books", {headers: {'Access-Control-Allow-Origin': 'http://localhost:5173/'}});
if (res.status !== 200) {
return new Error("Internal Server error");
}
const data = await res.data.books;
return data
}
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 want to create basic Next.js HOC for authentication. I searched but I didn't figure it out.
I have an admin page in my Next.js app. I want to fetch from http://localhost:4000/user/me and that URL returns my user. If user data returns, component must be rendered. If data didn't return, I want to redirect to the /admin/login page.
I tried this code but that didn't work. How can I solve this issue? Also can I use useSWR instead of fetch?
const withAuth = (Component, { data }) => {
if (!data) {
return {
redirect: {
destination: "/admin/login",
},
};
}
return Component;
};
withAuth.getInitialProps = async () => {
const response = await fetch("http://localhost:4000/user/me");
const data = await response.json();
return { data };
};
export default withAuth;
const AdminHome = () => {
return ();
};
export default withAuth(AdminHome);
Server-side authentication
Based on the answer from Create a HOC (higher order component) for authentication in Next.js, you can create a re-usable higher-order function for the authentication logic.
If the user data isn't present it'll redirect to the login page. Otherwise, the function will continue on to call the wrapped getServerSideProps function, and will return the merged user data with the resulting props from the page.
export function withAuth(gssp) {
return async (context) => {
const response = await fetch('http://localhost:4000/user/me');
const data = await response.json();
if (!data) {
return {
redirect: {
destination: '/admin/login'
}
};
}
const gsspData = await gssp(context); // Run `getServerSideProps` to get page-specific data
// Pass page-specific props along with user data from `withAuth` to component
return {
props: {
...gsspData.props,
data
}
};
}
}
You can then use it on the AdminHome page to wrap the getServerSideProps function.
const AdminHome = ({ data }) => {
return ();
};
export const getServerSideProps = withAuth(context => {
// Your normal `getServerSideProps` code here
return { props: {} };
});
export default AdminHome;
Client-side authentication
If you'd rather have the authentication done on the client, you can create a higher-order component that wraps the component you want to protect.
const withAuth = (Component) => {
const AuthenticatedComponent = () => {
const router = useRouter();
const [data, setData] = useState()
useEffect(() => {
const getUser = async () => {
const response = await fetch('http://localhost:4000/user/me');
const userData = await response.json();
if (!userData) {
router.push('/admin/login');
} else {
setData(userData);
}
};
getUser();
}, []);
return !!data ? <Component data={data} /> : null; // Render whatever you want while the authentication occurs
};
return AuthenticatedComponent;
};
You can then use it to wrap the AdminHome component directly.
const AdminHome = () => {
return ();
};
export default withAuth(AdminHome);
If you're looking for the typescript version:
withAuth.ts
export function withAuth(gssp: GetServerSideProps): GetServerSideProps {
return async (context) => {
const { user } = (await getSession(context.req, context.res)) || {};
if (!user) {
return {
redirect: { statusCode: 302, destination: "/" },
};
}
const gsspData = await gssp(context);
if (!("props" in gsspData)) {
throw new Error("invalid getSSP result");
}
return {
props: {
...gsspData.props,
user,
},
};
};
}
Home.tsx
export const getServerSideProps = withAuth(async (context) => {
return { props: {} };
});
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 currently have a firebase file setup like so:
class Firebase {
constructor() {
app.initializeApp(firebaseConfig);
this.auth = app.auth();
this.db = app.database();
this.storage = app.storage();
}
// *** Auth API ***
doCreateUserWithEmailAndPassword = (email, password) =>
this.auth.createUserWithEmailAndPassword(email, password);
This is just part of the file. To access it I setup a context
export const withFirebase = Component => props => (
<FirebaseContext.Consumer>
{firebase => <Component {...props} firebase={firebase} />}
</FirebaseContext.Consumer>
)
I then can wrap any component with withFirebase bada bing bada boom, it works.
However I'm trying out a redux type of library (react sweet state) which is pretty much a few js consts. In example:
const initialState = {
orgID: null,
memberID: 'blah here',
data: [],
}
const actions = {
fetchOrg: () => async ({ setState }) => {
const callFirebase = await Firebase.auth.onAuthStateChanged(authUser => {
if(authUser) {
//do some stuff
} else {
return false;
}
})
},
}
How can I use the firebase class instance in this setting?
I tried const firebaseAuth = new Firebase();
I get this error: Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).
My solution to this ended up being just passing firebase as a prop to the action, which I believe is the "reacty" way of doing it:
const actions = {
fetchOrg: firebase => async ({ setState }) => {
const callFirebase = await Firebase.auth.onAuthStateChanged(authUser => {
if(authUser) {
//do some stuff
} else {
return false;
}
})
},
}