Next.js reference error "client" is not defined - next.js

I am having an error when trying to fetch product data from sanity with getStaticPaths
here is my code:
import React, { useState } from "react";
function ProductPage({ product, products }) {
const { image, name, details, price } = product;
return (
<div className="pdPage">
<div className="container">{name}</div>
</div>
);
}
export default ProductPage;
export const getStaticPaths = async () => {
const query = `*[_type == "product"] {
slug {
current
}
}
`;
const products = await client.fetch(query);
const paths = products.map((product) => ({
params: {
slug: product.slug.current,
},
}));
return {
paths,
fallback: "blocking",
};
};
export const getStaticProps = async ({ params: { slug } }) => {
const query = `*[_type == "product" && slug.current == '${slug}'][0]`;
const productsQuery = '*[_type == "product"]';
const product = await client.fetch(query);
const products = await client.fetch(productsQuery);
console.log(product);
return {
props: { products, product },
};
};
And then I get reference error client is not defined from getstatic client.fetch
I even delete my code and replace from tutor github repository, but get the same error

After figure it out sometimes I found that I forgot to import client from

Related

How to update page when data is changed in nextjs?

When I delete one of the notes, it deletes from the DB. And to see the effect, I need to reload the page every time I delete a note.
How do I see the not deleted notes without reloading the page?
Here's the code for my page:
export default function Home(notes) {
const [notesData, setNotesData] = useState(notes);
const deleteNote = async (note) => {
const res = await fetch(`http://localhost:3000/api/${note}`, {
method: "DELETE",
});
};
return (
<div>
<h1>Notes:</h1>
{notesData.notes.map((note) => {
return (
<div className="flex">
<p>{note.title}</p>
<p onClick={() => deleteNote(note.title)}>Delete</p>
</div>
);
})}
</div>
);
}
export async function getServerSideProps() {
const res = await fetch(`http://localhost:3000/api`);
const { data } = await res.json();
return { props: { notes: data } };
}
If you're fetching the data with getServerSideProps you need to recall that in order to get the updated data like this :
import { useRouter } from 'next/router';
const router = useRouter()
const refreshData = () => router.replace(router.asPath);
But also you can store the data from getServerSideProps in a state and render that state and trigger a state update after a note is deleted like this :
export default function Home(notes) {
const [notesData, setNotesData] = useState(notes);
const deleteNote = async (note) => {
const res = await fetch(`http://localhost:3000/api/${note}`, {
method: "DELETE",
});
};
return (
<div>
<h1>Notes:</h1>
{notesData.notes.map((note) => {
return (
<div className="flex">
<p>{note.title}</p>
<p onClick={() => deleteNote(note.title).then(()=>{
const res = await fetch(`http://localhost:3000/api`);
const { data } = await res.json();
setNotesData(data)
})
}>Delete</p>
</div>
);
})}
</div>
);
}
export async function getServerSideProps() {
const res = await fetch(`http://localhost:3000/api`);
const { data } = await res.json();
return { props: { notes: data } };
}

Syntax Error: Invalid number, expected digit but got: "t". while passing a slug url to the graphql query function (nextjs app)

I'm having this error while I'm trying to get the right content according to the url. This is my query function:
const getSlugQuery = (slug) => {
const SLUG_QUERY = gql`
query ($slug: String!) {
postCollection(where:{slug:${slug}}) {
items {
title
shortDescription
heroImg {
url
}
createdAt
slug
}
}
}
`;
return SLUG_QUERY;
};
my getStaticprops where I pass the context.params.id:
export const getStaticProps = async (context) => {
const apolloClient = initializeApollo();
const { data } = await apolloClient.query({
query: getSlugQuery(context.params.id)
});
and the getStaticPath to prerender the cases:
export async function getStaticPaths() {
const apolloClient = initializeApollo();
const { data } = await apolloClient.query({
query: POSTS_QUERY
});
const paths = data.postCollection.items.map((post) => ({
params: { id: post.slug },
}))
return { paths, fallback: false }
}
If I try to pass an hardcoded url to the query, the response is working. What am I doing wrong there?
thanks
I believe you need to change your implementation to pass variables
Change getSlugQuery to not take any parameters:
const getSlugQuery = () => {
const SLUG_QUERY = gql`
query ($slug: String!) {
postCollection(where:{slug:$slug}) {
items {
title
shortDescription
heroImg {
url
}
createdAt
slug
}
}
}
`;
return SLUG_QUERY;
};
Then change getStaticProps to pass the slug as a variable to the apolloclient:
export const getStaticProps = async (context) => {
const apolloClient = initializeApollo();
const { data } = await apolloClient.query({
query: getSlugQuery(),
variables: {
slug: context.params.id,
},
});
};

Dynamic Content not loading in NextJs Dynamic Pages

Currently I have a Blogs collection type in my Strapi CMS with an Id and title data fields. I'm using NextJs for my frontend to dynamically load blog content for each blog page. But my content doesn't load when my dynamic page is loaded.
Page where individual blogs are stored:
{posts &&
posts.map((item, idx) => (
<Link href={`/BlogPage/${item.id}`}>
<div>
<img src={`http://localhost:1337${item.Thumbnail.url}`}/>
</div>
</Link>
Then inside my BlogPage directory i have a file [id].js:
export default function name({blog}) {
return (
<>
<div>
{blog.Title}
</div>
</>
)}
// Tell nextjs how many pages are there
export async function getStaticPaths() {
const res = await fetch("http://localhost:1337/blogs");
const posts = await res.json();
const paths = posts.map((blog) => ({
params: { id: blog.id.toString() },
}));
return {
paths,
fallback: false,
};
}
// Get data for each individual page
export async function getStaticProps({ params }) {
const { id } = params;
const res = await fetch(`http://localhost:1337/blogs?id=${id}`);
const data = await res.json();
const posts = data[0];
return {
props: { posts },
};
}
This takes me to this URL http://localhost:3000/BlogPage/1 and gives me an error
TypeError: Cannot read property 'Title' of undefined
Try to out the getStaticProps and getStaticPaths of name function
export async function getStaticPaths() {
const res = await fetch("http://localhost:1337/blogs");
const posts = await res.json();
const paths = posts.map((blog) => ({
params: { id: blog.id.toString() },
}));
return {
paths,
fallback: false,
};
}
export async function getStaticProps({ params }) {
const { id } = params;
const res = await fetch(`http://localhost:1337/blogs?id=${id}`);
const data = await res.json();
const posts = data[0];
return {
props: { posts },
};
}
export default function name({posts }) { // change this line
return (
<>
<div>
{posts.Title} // change this line // Are you sure is it Title? not title? if it is with lowercase, it will return null
</div>
</>
)
}

too many renders react hooks, useEffect, map

hey guys I'm trying to add to sum a simple product price for each iteration and notice its runs too manytimes. in the use effect I have to listen to the UserCart. can't do it with an empty array/[userCart].
also I get the following error in more components which i couldnt fix for the same reason with the last argument in the useEffect:
update: I did divide the useEffect as suggusted but it with [userCart ] it enter infinite loop
the code:
import React, { useState, useEffect } from 'react'
import firebase from 'firebase';
import { useAuth, useStoreUpdate } from '../contexts/FirebaseContext';
export default function Cart() {
const [userMail, setUserMail] = useState(undefined)
const [userCart, setUserCart] = useState(undefined)
const [totalAmmout, setTotalAmmout] = useState(0)
const user = useAuth()
const userDoc = firebase.firestore().collection("cart").doc(userMail)
const updateStore = useStoreUpdate()
const updateCart = () => {
userDoc.get().then((doc) => {
if (doc.exists) {
let cart = doc.data()
setUserCart(cart)
}
})
}
async function removeFromCart(itemId, name, url, price, category, type, description) {
const cartItem = { itemId, name, url, price, category, type, description }
await userDoc.update({
item: firebase.firestore.FieldValue.arrayRemove(cartItem)
})
await updateCart()
await updateStore()
}
useEffect(() => {
if (user.currentUser) {
setUserMail(user.currentUser.email)
updateCart()
updateStore()
}
},userCart)
if (!userCart) return <h1>hold</h1>
let total = 0
return (
<main className="main-cart">
<div className="container">
{userCart.item && userCart.item.length >= 1 && userCart.item.map((item, i, arr) => {
console.log(item); //it runs more than 20 times
return (
< div className="item-container" key={item.itemId} >
<h3>{item.name}</h3>
<p>{item.price}</p>
<img height="150px" width="150px" src={item.url} alt="" />
<button onClick={async () => {
await removeFromCart(item.itemId, item.name, item.url, item.price, item.category, item.type, item.description)
}}>X</button>
</div>
)
})}
</div>
<div className="fixed-bottom-link">
<button>finish purchase</button>
</div>
</main >
)
}
edit :
I found the major component, but still couldnt make it work
the 2nd useEffect [userCart] wont run, it works only when not in the array, but it enter a loop:
import React, { useContext, useState, useEffect } from 'react';
import { auth } from '../firebase/firebase';
import firebase from '../firebase/firebase';
const FirebaseContext = React.createContext()
const StoreContext = React.createContext()
const StoreContextUpdate = React.createContext()
export function useAuth() {
return useContext(FirebaseContext)
}
export function useStore() {
return useContext(StoreContext)
}
export function useStoreUpdate() {
return useContext(StoreContextUpdate)
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true)
const [userMail, setUserMail] = useState(undefined)
const [userCart, setUserCart] = useState(undefined)
const userDoc = firebase.firestore().collection("cart").doc(userMail)
const updateCart = () => {
userDoc.get().then((doc) => {
if (doc.exists) {
let cart = doc.data()
setUserCart(cart)
}
})
}
function signup(email, password) {
return auth.createUserWithEmailAndPassword(email, password)
}
function login(email, pasword) {
return auth.signInWithEmailAndPassword(email, pasword)
}
function logout() {
return auth.signOut()
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async user => {
await setCurrentUser(user)
await setLoading(false)
})
return unsubscribe
}, [userCart])
useEffect(()=>{
async function refreshCart(){
await setUserMail(currentUser.email);
await updateCart()
}
if (currentUser && currentUser.email) {
refreshCart()
}
return
},[userCart])
const value = {
currentUser,
signup,
login,
logout
}
return (
<FirebaseContext.Provider value={value}>
<StoreContext.Provider value={userCart && userCart.item}>
<StoreContextUpdate.Provider value={updateCart}>
{!loading && children}
</StoreContextUpdate.Provider>
</StoreContext.Provider>
</FirebaseContext.Provider>
)
}
useEffect(() => {
if (user.currentUser) {
setUserMail(user.currentUser.email);
updateCart();
updateStore();
}
}, [userCart]); //<-- userCart needs to be inside array
and the reason for too many rerenders is you're calling updateCart function when the userCart changes and then userCart change will cause the call of updateCart again and this loop will repeat itself until you're out of memory
I think adding another useEffect might help you
useEffect(() => {
if (user.currentUser) {
setUserMail(user.currentUser.email);
updateStore();
}
}, [userCart]); //<-- userCart needs to be inside array
useEffect(() => {
if (user.currentUser) {
updateCart();
}
}, []);

How do I use redux toolkit's createSlice to create localStorage?

I am making a cart functionality using redux toolkit's createSlice. But in the browser's application tab, the value of the localStorage is showing as [object Object]. Can someone help me with this please?
cartSlice.js
import { createSlice } from '#reduxjs/toolkit';
import axios from 'axios'
const cartItemsFromStorage = localStorage.getItem('cartItems') ? localStorage.getItem('carts') : []
export const cartSlice = createSlice({
name: 'cart',
initialState: {
cartItems: cartItemsFromStorage,
},
reducers: {
add: (state, action) => {
const item = action.payload
const existItem = state.cartItems.find(x => x.product === item.product)
if (existItem) {
const currentItems = state.cartItems.map(x => x.product === existItem.product ? item : x)
state.cartItems = [...currentItems]
} else {
state.cartItems = [...state.cartItems, item]
localStorage.setItem('cartItems', state.cartItems)
}
},
// remove: (state, action) => {
// },
},
});
const { add } = cartSlice.actions;
export const selectCartItems = state => state.cart.cartItems;
export const addToCart = (id, qty) => async (dispatch) => {
const { data } = await axios.get(`/api/products/${id}`)
dispatch(add({
product: data._id,
name: data.name,
imgae: data.imgae,
price: data.price,
countInStock: data.countInStock,
qty
}))
}
export default cartSlice.reducer;
CartScreen.js
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { selectCartItems, addToCart } from '../features/cartSlice'
const CartScreen = ({ match, location, history }) => {
const productId = match.params.id
const qty = location.search ? Number(location.search.split('=')[1]) : 1
const dispatch = useDispatch()
const cartItems = useSelector(selectCartItems)
useEffect(() => {
if (productId) {
dispatch(addToCart(productId, qty))
}
console.log(`cartItems: ${cartItems}`)
}, [dispatch, productId, qty])
return (
<div>
Cart
</div>
)
}
export default CartScreen
The next time I refresh the page, the initialState is not there, insted it shows [object Object]. I know the problem is with localStorage. Please correct me.
I think the problem is you are doing localStorage stuff in a reducer action. Reducer only can do simple operations and modify the state, so I encourage you to try to pass localStorage calls into the thunk action.

Resources