I was trying to build a collaborative online code editor. Where I want to update all screen other than the one who made change each time a change is made in code editor.
The objective is to:
[code editor]
Firebase:
But when I use the onValue listener for the firebase an infinite loop sets in. How can I fix it?
There are two main files:
-Landing.js
import React, { useEffect, useState, useCallback } from "react";
import CodeEditorWindow from "./CodeEditorWindow";
import axios from "axios";
import { classnames } from "../utils/general";
import { languageOptions } from "../constants/languageOptions";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { defineTheme } from "../lib/defineTheme";
import useKeyPress from "../hooks/useKeyPress";
import OutputWindow from "./OutputWindow";
import CustomInput from "./CustomInput";
import OutputDetails from "./OutputDetails";
import ThemeDropdown from "./ThemeDropdown";
import LanguagesDropdown from "./LanguagesDropdown";
import { initializeApp } from "firebase/app";
import { getDatabase, ref,onValue } from "firebase/database";
const javascriptDefault = `/**
* Problem: Binary Search: Search a sorted array for a target value.
*/
// Time: O(log n)
const binarySearch = (arr, target) => {
return binarySearchHelper(arr, target, 0, arr.length - 1);
};
const binarySearchHelper = (arr, target, start, end) => {
if (start > end) {
return false;
}
let mid = Math.floor((start + end) / 2);
if (arr[mid] === target){
return mid;
}
if (arr[mid] < target) {
return binarySearchHelper(arr, target, mid + 1, end);
}
if (arr[mid] > target) {
return binarySearchHelper(arr, target, start, mid - 1);
}
};
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const target = 5;
console.log(binarySearch(arr, target));
`;
const Landing = () => {
const [code, setCode] = useState(javascriptDefault);
const [customInput, setCustomInput] = useState("");
const [outputDetails, setOutputDetails] = useState(null);
const [processing, setProcessing] = useState(null);
const [theme, setTheme] = useState("cobalt");
const [language, setLanguage] = useState(languageOptions[0]);
const enterPress = useKeyPress("Enter");
const ctrlPress = useKeyPress("Control");
const onSelectChange = (sl) => {
console.log("selected Option...", sl);
setLanguage(sl);
};
const firebaseConfig = {
apiKey: "xyz",
authDomain: "xyz.firebaseapp.com",
databaseURL: "https://xyz-default-rtdb.firebaseio.com",
projectId: "xyz",
storageBucket: "xyz.appspot.com",
messagingSenderId: "94230328",
appId: "1:907394230328:web:74604ad262f6decb171ecd"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const db = getDatabase(app);
onValue(ref(db, 'Content/' + language),(snapshot) => {
const data = snapshot.val();
if (data) {
setCode(data);
}
});
const checkStatus = useCallback(async (token) => {
const options = {
method: "GET",
url: process.env.REACT_APP_RAPID_API_URL + "/" + token,
params: { base64_encoded: "true", fields: "*" },
headers: {
"X-RapidAPI-Host": process.env.REACT_APP_RAPID_API_HOST,
"X-RapidAPI-Key": process.env.REACT_APP_RAPID_API_KEY,
},
};
try {
let response = await axios.request(options);
let statusId = response.data.status?.id;
// Processed - we have a result
if (statusId === 1 || statusId === 2) {
// still processing
setTimeout(() => {
checkStatus(token);
}, 2000);
return;
} else {
setProcessing(false);
setOutputDetails(response.data);
showSuccessToast(`Compiled Successfully!`);
console.log("response.data", response.data);
return;
}
} catch (err) {
console.log("err", err);
setProcessing(false);
showErrorToast();
}
}, []);
const handleCompile = useCallback(() => {
setProcessing(true);
const formData = {
language_id: language.id,
// encode source code in base64
source_code: btoa(code),
stdin: btoa(customInput),
};
const options = {
method: "POST",
url: process.env.REACT_APP_RAPID_API_URL,
params: { base64_encoded: "true", fields: "*" },
headers: {
"content-type": "application/json",
"Content-Type": "application/json",
"X-RapidAPI-Host": process.env.REACT_APP_RAPID_API_HOST,
"X-RapidAPI-Key": process.env.REACT_APP_RAPID_API_KEY,
},
data: formData,
};
axios
.request(options)
.then(function (response) {
console.log("res.data", response.data);
const token = response.data.token;
checkStatus(token);
})
.catch((err) => {
let error = err.response ? err.response.data : err;
// get error status
let status = err.response.status;
console.log("status", status);
if (status === 429) {
console.log("too many requests", status);
showErrorToast(
`Quota of 100 requests exceeded for the Day! Please read the blog on freeCodeCamp to learn how to setup your own RAPID API Judge0!`,
10000
);
}
setProcessing(false);
console.log("catch block...", error);
});
}, [checkStatus, code, customInput, language.id]);
useEffect(() => {
if (enterPress && ctrlPress) {
console.log("enterPress", enterPress);
console.log("ctrlPress", ctrlPress);
handleCompile();
}
}, [ctrlPress, enterPress, handleCompile]);
const onChange = (action, data) => {
switch (action) {
case "code": {
setCode(data);
break;
}
default: {
console.warn("case not handled!", action, data);
}
}
};
function handleThemeChange(th) {
const theme = th;
console.log("theme...", theme);
if (["light", "vs-dark"].includes(theme.value)) {
setTheme(theme);
} else {
defineTheme(theme.value).then((_) => setTheme(theme));
}
}
useEffect(() => {
defineTheme("oceanic-next").then((_) =>
setTheme({ value: "oceanic-next", label: "Oceanic Next" })
);
}, []);
const showSuccessToast = (msg) => {
toast.success(msg || `Compiled Successfully!`, {
position: "top-right",
autoClose: 1000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
};
const showErrorToast = (msg, timer) => {
toast.error(msg || `Something went wrong! Please try again.`, {
position: "top-right",
autoClose: timer ? timer : 1000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
};
return (
<>
<ToastContainer
position="top-right"
autoClose={2000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
/>
<div className="h-4 w-full bg-gradient-to-r from-pink-500 via-red-500 to-yellow-500"></div>
<div className="flex flex-row">
<div className="px-4 py-2">
<LanguagesDropdown onSelectChange={onSelectChange} />
</div>
<div className="px-4 py-2">
<ThemeDropdown handleThemeChange={handleThemeChange} theme={theme} />
</div>
</div>
<div className="flex flex-row space-x-4 items-start px-4 py-4">
<div className="flex flex-col w-full h-full justify-start items-end">
<CodeEditorWindow
code={code}
onChange={onChange}
language={language?.value}
theme={theme.value}
/>
</div>
<div className="right-container flex flex-shrink-0 w-[30%] flex-col">
<OutputWindow outputDetails={outputDetails} />
<div className="flex flex-col items-end">
<CustomInput
customInput={customInput}
setCustomInput={setCustomInput}
/>
<button
onClick={handleCompile}
disabled={!code}
className={classnames(
"mt-4 border-2 border-black z-10 rounded-md shadow-[5px_5px_0px_0px_rgba(0,0,0)] px-4 py-2 hover:shadow transition duration-200 bg-white flex-shrink-0",
!code ? "opacity-50" : ""
)}
>
{processing ? "Processing..." : "Compile and Execute"}
</button>
</div>
{outputDetails && <OutputDetails outputDetails={outputDetails} />}
</div>
</div>
</>
);
};
export default Landing;
-CodeEditor.js
import React, { useState } from "react";
import Editor from "@monaco-editor/react";
import { initializeApp } from "firebase/app";
import { getDatabase, ref, set, get } from "firebase/database";
const firebaseConfig = {
apiKey: "xyz",
authDomain: "xyz.firebaseapp.com",
databaseURL: "https://xyz-default-rtdb.firebaseio.com",
projectId: "xyz",
storageBucket: "xyz.appspot.com",
messagingSenderId: "94230328",
appId: "1:907394230328:web:74604ad262f6decb171ecd"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const db = getDatabase(app);
const CodeEditorWindow = ({ onChange, language, code, theme }) => {
const [value, setValue] = useState(code || "");
const handleEditorChange = (value) => {
get(ref(db, 'Content/' + language)).then((snapshot) => {
if (snapshot.exists()) {
if(snapshot.val() !== value){
set(ref(db, 'Content/' + language), {
code: value
});
setValue(value);
onChange("code", value);
}
else{
setValue(value);
onChange("not", value);
}
} else {
set(ref(db, 'Content/' + language), {
code: value
});
setValue(value);
onChange("code", value);
}
}).catch((error) => {
console.error(error);
});
};
return (
<div className="overlay rounded-md overflow-hidden w-full h-full shadow-4xl">
<Editor
height="85vh"
width={`100%`}
language={language || "javascript"}
value={value}
theme={theme}
defaultValue="// some comment"
onChange={handleEditorChange}
/>
</div>
);
};
export default CodeEditorWindow;
In the handleEditorChange
compare the new contents of the editor and the last value you got from the database. Only write to the database when they are different.
In general, you'll want to also consider using a transaction for this as there may be multiple users making changes around the same time. To allow concurrent updates to the documents, you'll need a concurrent editing solution such as the one Firepad uses.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.