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

Redux Toolkit + Next.js页面无限重渲染循环问题求助

问题:Next.js + Redux 页面无限重渲染冻结标签页
  • 现象:localhost:3000/chat页面触发无限重渲染循环,导致标签页冻结;相同逻辑在其他页面的Redux slice中可正常运行
  • 已尝试排查:切换前后端执行环境、移除其他reducer仅保留问题reducer、调整useEffect依赖项、修改slice状态逻辑、将页面移至首页排除路由影响,均未解决
  • 关键线索:注释掉const { messages } = useAppSelector(state => state.chatReducer);或dispatch(setMessages(messageRes.data.messageSents));即可消除循环

相关代码

Chat页面代码

'use client';
import { setMessages } from '@/lib/redux/slices/chat';
import { setErrors } from '@/lib/redux/slices/errors';
import { useAppDispatch, useAppSelector } from '@/lib/redux/store';
import { useEffect } from 'react';

export default async function Chat() {
    const { messages } = useAppSelector(state => state.chatReducer);

    const dispatch = useAppDispatch();

    console.log('CHAT');

    const getMessages = async () => {
        try {
            const query = `query {
  messageSents(orderBy:blockNumber) {
    id
    sender
    _theMessage
    blockNumber
  }
}`;

        const messageRes = await (
            await fetch('https://api.thegraph.com/subgraphs/name/moneydev1111/mychatapp', {
                method: 'POST',
                body: JSON.stringify({ query }),
            })
        ).json();

        dispatch(setMessages(messageRes.data.messageSents));
    } catch (error) {
        dispatch(setErrors([error]));
    }
};

useEffect(() => {
    getMessages();

    // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);


return (
    <div></div>
);

Chat Slice代码

import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface MessageReq {
    id: string;
    sender: string;
    _theMessage: string;
    blockNumber: string;
}
interface InitialState {
    messages: MessageReq[];
}

const initialState: InitialState = {
    messages: [],
};

export const chat = createSlice({
    name: 'chat',
    initialState,
    reducers: {
        setMessages: (state, action: PayloadAction<MessageReq[]>) => {
            state.messages.push(...action.payload);
        },
    },
});

export const { setMessages } = chat.actions;

export default chat.reducer;

Store代码

import { configureStore } from '@reduxjs/toolkit';

import walletReducer from './slices/wallet';
import errorsReducer from './slices/errors';
import chatReducer from './slices/chat';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';

export const store = configureStore({
    reducer: {
        walletReducer,
        errorsReducer,
        chatReducer,
    },
    middleware: getDefaultMiddleware => getDefaultMiddleware({ serializableCheck: false }),
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

Provider代码

'use client';

import { store } from './store';
import { Provider } from 'react-redux';

export function ReduxProvider({ children }: { children: React.ReactNode }) {
    return <Provider store={store}>{children}</Provider>;
}

Layout代码

import './globals.css';
import './chat.css';
import { Inter } from 'next/font/google';
import Navbar from '@/components/Navbar';
import { ReduxProvider } from '@/lib/redux/provider';
import ChainList from '@/components/ChainList';
import Script from 'next/script';
import CustomScrollBar from '@/components/CustomScrollBar';

const inter = Inter({ subsets: ['latin'] });

export const metadata = {
    title: 'Create Next App',
    description: 'Generated by create next app',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
        <html lang="en">
            <body className={inter.className}>
                <ReduxProvider>
                    <CustomScrollBar />
                    <Navbar />
                    {children}
                    {/* <ChainLogos /> */}
                    <ChainList />
                    {/* <Footer /> */}
                </ReduxProvider>
                <Script
                    src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
                    integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
                    crossOrigin="anonymous"
                    defer
                ></Script>
            </body>
        </html>
    );

}

问题根源

  1. 客户端组件类型错误:标注了'use client'的Chat组件被声明为async function,Next.js对异步客户端组件的特殊处理会导致每次状态更新都重新执行整个组件函数,触发无限循环。
  2. 状态更新的连锁反应:dispatch更新messages后,useAppSelector检测到状态变化触发组件重渲染;异步组件的重复执行又会重新触发useEffect中的请求与dispatch,形成闭环。
  3. slice状态逻辑缺陷:当前setMessages使用push(...action.payload)会持续追加相同数据,即使解决了循环问题,也会导致messages数组无限膨胀。

解决方案

1. 移除Chat组件的async关键字

客户端组件不能是异步函数,修改组件声明:

export default function Chat() { // 移除async
    // ... 原有代码
}

2. 修正slice的状态更新逻辑

将追加数组改为直接替换,避免重复数据累积:

setMessages: (state, action: PayloadAction<MessageReq[]>) => {
    state.messages = action.payload; // 替换而非追加
},

3. 修复useEffect依赖项

移除ESLint禁用注释,添加稳定的dispatch依赖:

useEffect(() => {
    getMessages();
}, [dispatch]); // Redux的dispatch是稳定引用,不会触发额外重渲染

内容的提问来源于stack exchange,提问作者Eugene1111

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 09:35:15