#State loss in frontend when start chat

1 messages · Page 1 of 1 (latest)

stray cairn
#

I create agent state in frontend NextJS and Langgraph Agent backend

export interface AgentManagedState {
    selectedClient: Client | null;
    salesID?: string;
}

So, when user select Client in frontend, I update state using

const handleClientIDChange = useCallback(
        (client: ClientType) => {
            // Normalize incoming client fields from API into our Client shape
            const numericId = (client as any).customerId ?? (client as any).customer_id;
           
            const normalized: ClientType = {
                ...client,
                customerId: typeof numericId === "number" ? numericId : (numericId ? Number(numericId) : undefined as unknown as number),
            } as ClientType;

            preservedDataRef.current = {};
            
            setAgentState((prevState: any) => {
                if (!prevState) {
                    return {
                        selectedClient: normalized,
                    };
                }
                return {
                    ...prevState,
                    selectedClient: normalized,
                };
            });
        }, [setAgentState]);

but when I log state in frontend, I see selectedClient return to null.
By the way, in Langgraph backend, I have code
await copilotkit_emit_state(config, state)
that I don't see in new tutorial.

cloud badgeBOT
#

To save your precious dev time, this solution has been AI generated

It sounds like your selectedClient state is being reset due to a disconnect between frontend and backend state management in your NextJS chat app.

Why selectedClient Resets to Null

CopilotKit's architecture requires that any state you want to persist (like selectedClient) must be managed through its shared state system—not just local React state. If you update selectedClient with useState, it will be overwritten by the agent's returned state after backend processing, causing it to reset to null.

Correct State Management Pattern

Frontend:

const { state, setState } = useCoAgent<{ selectedClient: Client | null }>({
  name: "client_manager_agent",
  initialState: { selectedClient: null }
});

const handleClientSelect = (client: Client) => {
  setState(prev => ({ ...prev, selectedClient: client }));
};

Backend:

import { copilotkitEmitState } from "@copilotkit/runtime";

async function selectClientNode(state, config) {
  // ...logic
  await copilotkitEmitState(config, { selectedClient });
  return { ...state, selectedClient };
}

Key Points:

  • Always update and read selectedClient through CopilotKit's state hooks.
  • Ensure backend nodes/functions return the updated state as the final source of truth.
  • Use copilotkitEmitState for real-time updates, but always return the state at the end.

References:

Was this helpful?

If this solution worked for you, please click on the appropriate option below to help us improve:

cloud badgeBOT
#

Hey @stray cairn, wanted to follow up - is everything working now or do you need any more help with this?

stray cairn
#

I follow the guide above replacing useCallBack + useState with useCoagent. But still got the same behavior. UseCoagent state keep changing back to null that I have to patch it with useRef or useMemo. It also happens when backend is not even running.

stray cairn
#

I think I solved it. It is a race condition that consecutive actions want to update state but prev state is not populated properly by the time the latter want to update, so I see state loss.

stray cairn
#

Having said that, after solved the problem, I see brief state loss when send chat message

export default function CopilotGlobalActions({ children, }: ChatBotProviderProps) {
    const { state: agentStateRaw, setState: setAgentStateRaw } = useCoAgent<AgentManagedState>({
        name: "basic_agent",
    });
    
    // EVIDENCE COLLECTION: Track when CopilotKit clears state (for GitHub issue)
    const rawStateLogRef = useRef<any[]>([]);
    const prevRawStateRef = useRef<any>(null);
    useEffect(() => {
        const currentState = {
            timestamp: new Date().toISOString(),
            selectedClient: agentStateRaw?.selectedClient?.customerId,
            portfolioCount: agentStateRaw?.portfolio?.positions?.length || 0,
            hasHealthMetrics: !!agentStateRaw?.healthMetrics,
        };
        
        const prevState = prevRawStateRef.current;
        
        // Only log when state goes from populated to empty (CopilotKit clearing)
        if (prevState && 
            (prevState.selectedClient || prevState.portfolioCount > 0) &&
            (!currentState.selectedClient && currentState.portfolioCount === 0)) {
            console.log('[EVIDENCE] ⚠️ CopilotKit CLEARED state:', {
                before: prevState,
                after: currentState,
            });
            rawStateLogRef.current.push({
                type: 'cleared',
                before: prevState,
                after: currentState,
            });
        }
     
        prevRawStateRef.current = currentState;
        
    }, [agentStateRaw]);
#
CopilotGlobalActions.tsx:65 [EVIDENCE] :warning: CopilotKit CLEARED state: 
  {before: {…}, after: {…}}
  after: hasHealthMetrics: falseportfolioCount: 0 selectedClient: undefined timestamp: "2025-10-21T07:23:44.269Z" [[Prototype]]: Object
  before: hasHealthMetrics: trueportfolioCount: 32 selectedClient: 39983 timestamp: "2025-10-21T07:23:40.275Z" [[Prototype]]: Object[[Prototype]]: Object

I know that it is cleared after sent message because I add time sleep in first Langgraph node. before sleep time, I see state lost in frontend, then after sleep I see state recovered.

async def reset_tool_status_node(state: GenerativeUIState, config: RunnableConfig):
    """
    First node
    """
    state["tool_status"] = []
    from asyncio import sleep
    print(f"before state['selectedClient']: {state['selectedClient']}")
    # sleep 2 seconds
    await sleep(2)
    print(f"after state['selectedClient']: {state['selectedClient']}")
    return state

But note that I see
before state['selectedClient']: {'customerId': 39983
after state['selectedClient']: {'customerId': 39983
from python backend terminal. Notice that selectedClient state is always there.

quaint mantle
#

I believe the state is recovered after some time. This is the intended behaviour of the state. But I believe this didn't affect any of your application logic as it will be updated correctly.

wheat vale
#

i am also experiencing this state loss, the state is set but once i send a message using sendMessage, that state set disappears and does not reach my agent

quaint mantle
#

From the code snippet you had shared, you havent set the initial state for the useCoagent() hook. This could be the reason for the issue. Please try setting the initial state and then running the agent.

wheat vale
#

okay would do and give you feedback

#

just did and it does not resolve the issue, noticed that threadId from useCoAgent hook is undefined until send message runs and returns a message. Could this be the reason

cloud badgeBOT