You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 01:54:55