diff --git a/frontend/app/(app)/code/[id]/page.tsx b/frontend/app/(app)/code/[id]/page.tsx
index 1fd90cc..d97025b 100644
--- a/frontend/app/(app)/code/[id]/page.tsx
+++ b/frontend/app/(app)/code/[id]/page.tsx
@@ -63,14 +63,6 @@ const CodeEditor = dynamic(() => import("@/components/editor"), {
loading: () => ,
})
-function getReactDefinitionFile() {
- const reactDefinitionFile = fs.readFileSync(
- "node_modules/@types/react/index.d.ts",
- "utf8"
- )
- return reactDefinitionFile
-}
-
export default async function CodePage({ params }: { params: { id: string } }) {
const user = await currentUser()
const sandboxId = params.id
@@ -94,8 +86,6 @@ export default async function CodePage({ params }: { params: { id: string } }) {
return notFound()
}
- const reactDefinitionFile = getReactDefinitionFile()
-
return (
@@ -104,7 +94,6 @@ export default async function CodePage({ params }: { params: { id: string } }) {
diff --git a/frontend/components/editor/index.tsx b/frontend/components/editor/index.tsx
index 05699f1..30c8d60 100644
--- a/frontend/components/editor/index.tsx
+++ b/frontend/components/editor/index.tsx
@@ -35,11 +35,9 @@ import { ImperativePanelHandle } from "react-resizable-panels"
export default function CodeEditor({
userData,
sandboxData,
- reactDefinitionFile,
}: {
userData: User
sandboxData: Sandbox
- reactDefinitionFile: string
}) {
const socketRef = useRef(null);
@@ -104,6 +102,16 @@ export default function CodeEditor({
const room = useRoom()
const [provider, setProvider] = useState()
const userInfo = useSelf((me) => me.info)
+
+ // Liveblocks providers map to prevent reinitializing providers
+ type ProviderData = {
+ provider: LiveblocksProvider;
+ yDoc: Y.Doc;
+ yText: Y.Text;
+ binding?: MonacoBinding;
+ onSync: (isSynced: boolean) => void;
+ };
+ const providersMap = useRef(new Map());
// Refs for libraries / features
const editorContainerRef = useRef(null)
@@ -332,43 +340,77 @@ export default function CodeEditor({
if (!editorRef || !tab || !model) return
- const yDoc = new Y.Doc()
- const yText = yDoc.getText(tab.id)
- const yProvider: any = new LiveblocksProvider(room, yDoc)
+ let providerData: ProviderData;
+
+ // When a file is opened for the first time, create a new provider and store in providersMap.
+ if (!providersMap.current.has(tab.id)) {
+ const yDoc = new Y.Doc();
+ const yText = yDoc.getText(tab.id);
+ const yProvider = new LiveblocksProvider(room, yDoc);
- const onSync = (isSynced: boolean) => {
- if (isSynced) {
- const text = yText.toString()
- if (text === "") {
- if (activeFileContent) {
- yText.insert(0, activeFileContent)
- } else {
- setTimeout(() => {
- yText.insert(0, editorRef.getValue())
- }, 0)
+ // Inserts the file content into the editor once when the tab is changed.
+ const onSync = (isSynced: boolean) => {
+ if (isSynced) {
+ const text = yText.toString()
+ if (text === "") {
+ if (activeFileContent) {
+ yText.insert(0, activeFileContent)
+ } else {
+ setTimeout(() => {
+ yText.insert(0, editorRef.getValue())
+ }, 0)
+ }
}
}
}
+
+ yProvider.on("sync", onSync)
+
+ // Save the provider to the map.
+ providerData = { provider: yProvider, yDoc, yText, onSync };
+ providersMap.current.set(tab.id, providerData);
+
+ } else {
+ // When a tab is opened that has been open before, reuse the existing provider.
+ providerData = providersMap.current.get(tab.id)!;
}
- yProvider.on("sync", onSync)
-
- setProvider(yProvider)
-
const binding = new MonacoBinding(
- yText,
+ providerData.yText,
model,
new Set([editorRef]),
- yProvider.awareness as Awareness
- )
+ providerData.provider.awareness as unknown as Awareness
+ );
+
+ providerData.binding = binding;
+
+ setProvider(providerData.provider);
return () => {
- yDoc.destroy()
- yProvider.destroy()
- binding.destroy()
- yProvider.off("sync", onSync)
- }
- }, [editorRef, room, activeFileContent])
+ // Cleanup logic
+ if (binding) {
+ binding.destroy();
+ }
+ if (providerData.binding) {
+ providerData.binding = undefined;
+ }
+ };
+ }, [room, activeFileContent]);
+
+ // Added this effect to clean up when the component unmounts
+ useEffect(() => {
+ return () => {
+ // Clean up all providers when the component unmounts
+ providersMap.current.forEach((data) => {
+ if (data.binding) {
+ data.binding.destroy();
+ }
+ data.provider.disconnect();
+ data.yDoc.destroy();
+ });
+ providersMap.current.clear();
+ };
+ }, []);
// Connection/disconnection effect
useEffect(() => {