1
\$\begingroup\$

The following is my React code to create a simple app containing a table which can be edited by multiple people at the same time. Each client connects to a WebSocket via a custom hook. The WebSocket server simply sends the newest data to the client when a connection is opened, and broadcasts any updates it receives.

I am new to React, so I would appreciate any feedback about my code.

App.tsx

import { useCallback, useEffect, useRef, useState } from "react";
import { type Data, default as DataGrid } from "./components/DataGrid.tsx";

function useWebSocket() {
  const server = useRef<WebSocket | null>(null);

  const [data, setData] = useState<Data>({
    head: [],
    body: [],
  });

  useEffect(() => {
    const socket = new WebSocket("ws://" + window.location.hostname + ":3000");

    socket.addEventListener("message", (ev) => {
      if (ev.data !== "") {
        setData(JSON.parse(ev.data));
      }
    });

    server.current = socket;

    return () => {
      socket.close();
    };
  }, []);

  const send = useCallback((data: Data) => {
    if (server.current?.readyState === WebSocket.OPEN) {
      server.current!.send(JSON.stringify(data));
    }
  }, []);

  return { data, send };
}

export default function App() {
  const { data, send } = useWebSocket();

  return <DataGrid data={data} onChange={send} />;
}

DataGrid.tsx

import { isEmpty } from "lodash";

export type Data = {
  head: Array<string>;
  body: Array<Record<string, string>>;
};

export default function DataGrid({
  data,
  onChange,
}: {
  data: Data;
  onChange: (data: Data) => void;
}) {
  function handleChange(record: number, column: string, string: string) {
    const copy = structuredClone(data);

    if (string !== "") {
      copy.body[record][column] = string;
    } else {
      delete copy.body[record][column];
    }

    onChange(copy);
  }

  // Make sure that there is always an empty row for new data.
  if (data.body.length === 0 || !isEmpty(data.body[data.body.length - 1])) {
    const copy = structuredClone(data);

    copy.body.push({});

    onChange(copy);
  }

  // https://getbootstrap.com/docs/5.3/content/tables/
  return (
    <div className="table-responsive">
      <table className="table">
        <thead>
          <tr>
            <th scope="col">#</th>
            {data.head.map((column) => (
              <th key={column} scope="col">
                {column}
              </th>
            ))}
          </tr>
        </thead>
        <tbody>
          {data.body.map((record, number) => (
            <tr key={number}>
              <th scope="row">{number + 1}</th>
              {data.head.map((column) => (
                <td key={column}>
                  <input
                    type="number"
                    className="form-control border-0 rounded-0 shadow-none p-0"
                    value={record[column] ?? ""}
                    onChange={(event) =>
                      handleChange(number, column, event.target.value)
                    }
                  />
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}
\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.