Next.js中createContext与useContext使用报错求助
Next.js中createContext与useContext使用报错求助
我是Next.js新手,在使用createContext和useContext时碰到错误,找不到问题根源。已经尝试在product/[slug]/page.tsx中添加"use client"但问题依旧,以下是相关代码及错误情况:
1. app/layout.tsx 代码
import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; import {StateContext} from "@/context/StateContext"; import { Toaster } from "react-hot-toast"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="en"> <body className={inter.className}> <StateContext> <Toaster></Toaster> {children} </StateContext> </body> </html> ); }
2. context/StateContext.tsx 代码
"use client"; import React,{createContext, useContext, useState, useEffect, Children} from "react"; import {toast} from'react-hot-toast'; import {product_attribute} from '@/components/Interface' export const Context = createContext<product_attribute| undefined>(undefined); export const StateContext = ({children} : any) =>{ const [showCart, setShowCart] = useState(false); const [cartItems, setCartItems] = useState(); const [totalPrice, setTotalPrice] = useState(); const [totalQuantities, setTotalQuantities] = useState(); const [qty, setQty] = useState(1); const incQty = () => { setQty((prevQty) => prevQty + 1); } const decQty = () => { setQty((prevQty) => { if(prevQty - 1 < 1) return 1; return prevQty - 1 }); } return( <Context.Provider value={{ qty, incQty, decQty, }}> {children} </Context.Provider> ) } export const useStateContext = () => { const context = useContext(Context); return context; };
3. components/Interface.tsx 代码
export interface SimplifiedProduct{ _id : string; imageUrl : string; name : string; price : number; slug : string; } export interface fullProduct{ id : string; image : any; details : string; name : string; price : number; slug : string; } export interface product_attribute{ qty : number, incQty : () => void, decQty : () => void, }
4. product/[slug]/page.tsx 代码
import React, {useContext} from 'react' import { Navbar, Footer } from '@/components' import { client, urlFor } from '@/lib/client' import { fullProduct, SimplifiedProduct } from '@/components/Interface'; import Image from 'next/image'; import { AiOutlineMinus, AiOutlinePlus, AiFillStar,AiOutlineStar} from 'react-icons/ai'; import {getProduct} from '@/components/Product'; import Product from '@/components/Product'; import ImageGallery from '@/components/ImageGallery'; import {Context} from '@/context/StateContext'; const ProductDetails = async({params}:{params : {slug : string}}) => { const data = await getData(params.slug); const products : SimplifiedProduct[] = await getProduct(); const {qty, incQty, decQty} = useContext(Context); return ( <div> <Navbar/> <div className='product-detail-container'> <div> <ImageGallery images={data.image}/> </div> <div className='product-detail-desc'> <h1>{data.name}</h1> <div className='reviews'> <div> <AiFillStar></AiFillStar> <AiFillStar></AiFillStar> <AiFillStar></AiFillStar> <AiFillStar></AiFillStar> <AiOutlineStar></AiOutlineStar> </div> <p> {20} </p> </div> <h4>Details : </h4> <p>{data.details}</p> <p className='price'>Rs{data.price}</p> <div className='quantity'> <h3>Quantity:</h3> <p className='quantity-desc'> <span className='minus' onClick={decQty}><AiOutlineMinus/></span> <span className='num' >{qty}</span> <span className='plus' onClick={incQty}><AiOutlinePlus/></span> </p> </div> <div className="buttons"> <button type="button" className="add-to-cart" >Add to Cart</button> <button type="button" className="buy-now" >Buy Now</button> </div> </div> </div> <div className="maylike-products-wrapper"> <h2>You may also like</h2> <div className="marquee"> <div className="maylike-products-container track"> {products.map((item:any) => ( <Product key={item._id} data={item} /> ))} </div> </div> </div> <Footer/> </div> ) } async function getData(slug :string){ const Query = ` *[_type == "product" && slug.current == "${slug}"][0]{ _id, name, price, image, slug, details, } `; const product : fullProduct = await client.fetch(Query); return product; } export default ProductDetails
错误原因
核心问题是在服务端组件中尝试使用客户端钩子useContext:Next.js 13+ App Router中,async组件默认是服务端组件,而useContext属于客户端专属钩子,只能在客户端组件中调用。即使添加了"use client",如果位置不正确也不会生效。
解决方案
1. 正确添加"use client"指令
必须将"use client"放在product/[slug]/page.tsx文件的最顶部,不能有任何代码、注释在它前面:
"use client"; // 这行必须是文件第一行 import React, {useContext} from 'react' // ... 其他导入和代码
注意:客户端组件不能是async函数,所以这种方案下需要把数据获取逻辑迁移到其他地方。
2. 拆分服务端与客户端逻辑(推荐方案)
将页面拆分为服务端父组件(负责数据获取)和客户端子组件(负责交互与上下文使用):
步骤1:修改product/[slug]/page.tsx为服务端组件
import ProductDetailsClient from './ProductDetailsClient'; import { client } from '@/lib/client'; import { fullProduct, SimplifiedProduct } from '@/components/Interface'; import { getProduct } from '@/components/Product'; export default async function ProductDetails({ params }: { params: { slug: string } }) { const data = await getData(params.slug); const products: SimplifiedProduct[] = await getProduct(); return <ProductDetailsClient data={data} products={products} />; } async function getData(slug: string) { const Query = ` *[_type == "product" && slug.current == "${slug}"][0]{ _id, name, price, image, slug, details, } `; const product: fullProduct = await client.fetch(Query); return product; }
步骤2:创建客户端子组件product/[slug]/ProductDetailsClient.tsx
"use client"; import React, { useContext } from 'react'; import { Navbar, Footer } from '@/components'; import { fullProduct, SimplifiedProduct } from '@/components/Interface'; import Image from 'next/image'; import { AiOutlineMinus, AiOutlinePlus, AiFillStar, AiOutlineStar } from 'react-icons/ai'; import Product from '@/components/Product'; import ImageGallery from '@/components/ImageGallery'; import { useStateContext } from '@/context/StateContext'; export default function ProductDetailsClient({ data, products }: { data: fullProduct, products: SimplifiedProduct[] }) { const context = useStateContext(); if (!context) { throw new Error('useStateContext must be used within a StateContext provider'); } const { qty, incQty, decQty } = context; return ( <div> <Navbar /> <div className='product-detail-container'> <div> <ImageGallery images={data.image} /> </div> <div className='product-detail-desc'> <h1>{data.name}</h1> <div className='reviews'> <div> <AiFillStar></AiFillStar> <AiFillStar></AiFillStar> <AiFillStar></AiFillStar> <AiFillStar></AiFillStar> <AiOutlineStar></AiOutlineStar> </div> <p>{20}</p> </div> <h4>Details : </h4> <p>{data.details}</p> <p className='price'>Rs{data.price}</p> <div className='quantity'> <h3>Quantity:</h3> <p className='quantity-desc'> <span className='minus' onClick={decQty}><AiOutlineMinus /></span> <span className='num'>{qty}</span> <span className='plus' onClick={incQty}><AiOutlinePlus /></span> </p> </div> <div className="buttons"> <button type="button" className="add-to-cart">Add to Cart</button> <button type="button" className="buy-now">Buy Now</button> </div> </div> </div> <div className="maylike-products-wrapper"> <h2>You may also like</h2> <div className="marquee"> <div className="maylike-products-container track"> {products.map((item: any) => ( <Product key={item._id} data={item} /> ))} </div> </div> </div> <Footer /> </div> ); }
这种拆分方式既符合Next.js App Router的设计理念,又解决了客户端钩子的使用限制问题。
内容的提问来源于stack exchange,提问作者Silent Prime
相关产品推荐
相关产品推荐

