I need to implement jspreadsheet in my project. My frontend is in ReactJS and my server is in NodeJS.
I need to use pusherjs to connect between server and client to get realtime changes in the sheet, but i dont know where i am getting wrong my sheet is not getting rendered.
REACT-JS CODE
import axios from "axios";
import client from "@jspreadsheet/client";
import formula from "@jspreadsheet/formula-pro";
import { useRef, useEffect, useState } from "react";
import { Spreadsheet, jspreadsheet } from "@jspreadsheet/react";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";
import Pusher from "pusher-js";
const license = {
clientId: "myClientId",
licenseKey:
"myLicenseKey",
};
jspreadsheet.setLicense(license);
jspreadsheet.setExtensions({ formula, client });
const initialGuid = "12aa4c90-791d-4a65-84a6-8ac55d6b1107";
const authEndpoint =
"baseURL/pusher/auth";
const pusher = new Pusher("PUSHERKEY", {
cluster: "ap2",
authEndpoint,
auth: {
headers: {
"Content-Type": "application/json",
"ngrok-skip-browser-warning": "true",
},
},
});
// Connect to the server using Pusher
const remote = client.connect({
transport: {
create: (guid) => {
console.log("???? ~ guid:", guid);
const channel = pusher.subscribe(`private-${guid}`);
channel.bind("pusher:subscription_succeeded", () => {
console.log(`Successfully subscribed to channel: private-${guid}`);
});
return channel;
},
send: (guid, event, data) => {
pusher.trigger(`private-${guid}`, event, data);
},
},
});
function App() {
const spreadsheet = useRef();
console.log("???? ~ App ~ spreadsheet:", spreadsheet);
const [guid, setGuid] = useState(initialGuid);
useEffect(() => {
const fetchSocketId = async () => {
// Ensure Pusher connection is established before fetching socketId
await new Promise((resolve) =>
pusher.connection.bind("connected", resolve)
);
const socketId = pusher.connection.socket_id;
const channel = "project-rfx"; // Replace with your actual channel name
if (socketId) {
try {
const response = await axios.post(
authEndpoint,
{
socket_id: socketId,
channel_name: channel,
},
{ headers: { "ngrok-skip-browser-warning": "true" } }
);
console.log("Authentication response:", response.data);
// Handle response, possibly storing it in Pusher client
} catch (error) {
console.error("Error authenticating with Pusher:", error);
}
} else {
console.log("No socket id");
}
};
fetchSocketId();
// Clean up if needed
return () => {
// Cleanup code if any
};
}, []);
useEffect(() => {
const fetchData = async () => {
try {
await remote.create(guid, {
tabs: true,
worksheets: [],
});
} catch (error) {
console.error("Error creating spreadsheet:", error);
}
};
fetchData();
}, [guid]);
useEffect(() => {
const channel = pusher.subscribe(`private-${guid}`);
channel.bind("spreadsheet-update", (data) => {
console.log("Received update:", data);
// Handle the update received from Pusher
});
return () => {
pusher.unsubscribe(`private-${guid}`);
};
}, [guid]);
const createNewSheet = async () => {
try {
const response = await axios.post(
"baseURL/clone",
{
sourceGuid: guid,
},
{ headers: { "ngrok-skip-browser-warning": "true" } }
);
const newGuid = response.data.newGuid;
setGuid(newGuid);
} catch (error) {
console.error("Error creating new sheet:", error);
}
};
return (
<div>
<button onClick={createNewSheet}>Create New Sheet</button>
<Spreadsheet ref={spreadsheet} guid={guid} />
</div>
);
}
export default App;
NODE-JS CODE
const express = require('express');
const http = require('http');
const server = require('@jspreadsheet/server');
const { createClient } = require('redis');
const Pusher = require('pusher');
const cors = require('cors');
const bodyParser = require('body-parser');
const app = express();
const redisClient = createClient({
socket: {
host: 'localhost',
port: 6379
},
});
redisClient.connect()
.then(console.log("redis is connected"))
.catch(console.error);
const license = {
clientId: 'myClientId',
licenseKey: 'myliscenceKey'
};
app.use(cors({
origin: 'http://localhost:5173', // Adjust this based on your React app's origin
credentials: true,
}));
app.use(bodyParser.json());
const httpServer = http.createServer(app);
const pusher = new Pusher({
appId: "1819901",
key: "PUSHERKEY",
secret: "SECRET",
cluster: "ap2",
useTLS: true
});
const defaultData = [
["Name", "Age", "City"],
["John Doe", 30, "New York"],
["Jane Smith", 25, "Los Angeles"],
["Mark Johnson", 40, "Chicago"]
];
function getConfigWithValues(config) {
return {
...config,
worksheets: config.worksheets.map(sheet => ({
...sheet,
minDimensions: sheet.minDimensions || [10, 10], // Ensure minDimensions has a default
columns: sheet.columns ? sheet.columns.map(col => ({ ...col })) : [],
rows: sheet.rows || {},
meta: sheet.meta || {},
comments: sheet.comments || {},
cells: sheet.cells || {},
cache: sheet.cache || {},
mergeCells: sheet.mergeCells || {},
style: sheet.style || {},
data: sheet.data ? sheet.data.map(row => row.map(cell => (typeof cell === 'object' && cell !== null ? { ...cell } : cell))) : []
}))
};
}
function applyChanges(config, changes) {
if (changes.method === 'setValue') {
const [row, col, value] = changes.args;
if (!config.worksheets[0].data[row]) {
config.worksheets[0].data[row] = [];
}
config.worksheets[0].data[row][col] = value;
}
}
server({
port: 3000,
config: {
cors: {
origin: "*",
},
},
error: async function(e) {
console.error(e);
process.exit(1);
},
auth: async function(socket) {
return true;
},
load: async function(guid) {
try {
const data = await redisClient.get(guid);
console.log(`Loading data for GUID: ${guid}`);
if (data) {
console.log(`Loaded data: ${data}`);
let config;
try {
config = JSON.parse(data);
if (!config.worksheets || !Array.isArray(config.worksheets) || config.worksheets.length === 0) {
config.worksheets = [{
worksheetId: 'e72ace94-f4ca-4228-a0c7-65d6a5a88cff',
minDimensions: [10, 10],
columns: [],
rows: [],
meta: {},
comments: {},
cells: {},
cache: {},
mergeCells: {},
style: {},
data: defaultData
}];
}
} catch (parseError) {
console.error(`Error parsing JSON for GUID ${guid}:`, parseError);
config = {
worksheets: [{
worksheetId: 'e72ace94-f4ca-4228-a0c7-65d6a5a88cff',
minDimensions: [10, 10],
columns: [],
rows: [],
meta: {},
comments: {},
cells: {},
cache: {},
mergeCells: {},
style: {},
data: defaultData
}]
};
}
return JSON.stringify(config);
} else {
return JSON.stringify({
worksheets: [{
worksheetId: 'e72ace94-f4ca-4228-a0c7-65d6a5a88cff',
minDimensions: [10, 10],
columns: [],
rows: [],
meta: {},
comments: {},
cells: {},
cache: {},
mergeCells: {},
style: {},
data: defaultData
}]
});
}
} catch (error) {
return JSON.stringify({
worksheets: [{
worksheetId: 'e72ace94-f4ca-4228-a0c7-65d6a5a88cff',
minDimensions: [10, 10],
columns: [],
rows: [],
meta: {},
comments: {},
cells: {},
cache: {},
mergeCells: {},
style: {},
data: defaultData
}]
});
}
},
create: async function(guid, config) {
try {
const exists = await redisClient.exists(guid);
if (exists) {
console.log(`Spreadsheet with GUID ${guid} already exists.`);
return false;
} else {
if (!config.worksheets || config.worksheets.length === 0) {
config.worksheets = [{
worksheetId: 'e72ace94-f4ca-4228-a0c7-65d6a5a88cff',
minDimensions: [10, 10], // Ensure minDimensions has a default
columns: [],
rows: [],
meta: {},
comments: {},
cells: {},
cache: {},
mergeCells: {},
style: {},
data: defaultData // Add default data here
}];
}
const configString = JSON.stringify(config);
await redisClient.set(guid, configString);
console.log(`Created spreadsheet with GUID: ${guid}`);
return true;
}
} catch (error) {
return false;
}
},
destroy: async function(guid) {
try {
await redisClient.del(guid);
return true;
} catch (error) {
return false;
}
},
change: async function(guid, changes) {
try {
const currentConfigString = await redisClient.get(guid);
if (!currentConfigString) {
return;
}
const currentConfig = JSON.parse(currentConfigString);
applyChanges(currentConfig, changes);
const updatedConfigString = JSON.stringify(currentConfig);
await redisClient.set(guid, updatedConfigString);
pusher.trigger('spreadsheet', 'configUpdated', { guid });
console.log(`Successfully updated spreadsheet with GUID ${guid}`);
} catch (error) {
console.error(`Error updating spreadsheet ${guid}:`, error);
}
},
license
});
app.post('/pusher/auth', (req, res) => {
const socketId = req.body.socket_id;
const channel = req.body.channel_name;
// Log the incoming request body for debugging
console.log('Pusher auth request body:', req.body);
if (!socketId || !channel) {
return res.status(400).send('Socket ID and channel name are required');
}
const auth = pusher.authorizeChannel(socketId, channel);
console.log(":rocket: ~ app.post ~ auth:", auth)
res.send(auth);
});
// Route to fetch spreadsheet data
app.get('/spreadsheet/:guid', async (req, res) => {
const guid = req.params.guid;
console.log(":rocket: ~ app.get ~ guid:", guid)
try {
const data = await redisClient.get(guid);
if (data) {
const parsedData = JSON.parse(data);
res.json(parsedData);
} else {
res.status(404).json({ error: 'Spreadsheet not found' });
}
} catch (error) {
console.error(`Error fetching spreadsheet data for GUID ${guid}:`, error);
res.status(500).json({ error: 'Internal server error' });
}
});
httpServer.listen(3001, () => {
console.log('Server is running on port 3001');
});
Can anyone please help me with any solution ?
Thanks in advance 🙂
New contributor
Amrit is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.