Closed
Description
Description
Was investigation why my chat re-render so much, and found that inside useChat
, handleInputChange
is created without useCallback
, resulting on different function every time the hook is called.
ai/packages/react/src/use-chat.ts
Lines 553 to 555 in 396d013
Exploration
export const Chat = ({
initialMessages,
onStreamingComplete,
}: {
initialMessages?: UIMessage[],
onStreamingComplete: (args: { chat: ReturnType<typeof useChat>; }) => void,
}) => {
// server state
const useChatOptions = useMemo<UseChatOptions>(
() => {
return {
id: 'FIXED',
api: 'https://...',
initialMessages: initialMessages ? initialMessages : undefined,
onError: error => {
console.error('An error occurred:', error);
toast.error('An error occurred. We are investingating! Try different model. \nCODE ' + error.message);
},
};
},
[
initialMessages,
]
);
const chat = useChat(useChatOptions);
useLogDifferencesBetweenPrevAndCurrentValue(chat); // Utility hook
return <ChatUI chat={chat} />;
};

Utility hook used for debug
I created this utility debug hook to check what changes between re-render
import { useEffect, useRef } from "react";
type AnyObject = { [k: string]: unknown; };
const getDifferences = (
prev: AnyObject,
current: AnyObject
) => {
type Difference = {
key: string,
type: 'added' | 'removed' | 'changed',
v: {
prev: unknown,
current: unknown,
},
isEqual_withObjectIs?: boolean,
isEqual_withJsonStringify?: boolean,
};
const differences: Difference[] = [];
// get all keys
const allKeys = Array.from(
new Set([...Object.keys(prev), ...Object.keys(current)])
);
// create diff
allKeys.forEach(key => {
const prevV = prev[key];
const currentV = current[key];
// if new prop
if (!prevV && currentV) {
differences.push({
key,
type: 'added',
v: {
prev: prevV,
current: currentV,
}
});
return;
}
// if removed prop
if (prevV && !currentV) {
differences.push({
key,
type: 'removed',
v: {
prev: prevV,
current: currentV,
}
});
return;
}
// if not changed
const isEqual_withObjectIs = Object.is(prevV, currentV);
const isEqual_withJsonStringify = JSON.stringify(prevV) === JSON.stringify(currentV);
if (isEqual_withObjectIs && isEqual_withJsonStringify) return;
// if changed
differences.push({
key,
type: 'changed',
v: {
prev: prevV,
current: currentV,
},
isEqual_withObjectIs,
isEqual_withJsonStringify
});
});
return differences;
};
export const useLogDifferencesBetweenPrevAndCurrentValue = (currentValue: AnyObject) => {
const prevValue = useRef(currentValue);
// on every render => log differences
useEffect(() => {
const differences = getDifferences(prevValue.current, currentValue);
prevValue.current = currentValue;
console.log({ differences });
});
return null;
};