Сохранить новые координаты при перемещении элемента в файл json по клику на кнопку (React JS)

У меня отрисовано несколько связанных между собой таблиц, которые могут перемещаться по экрану. Если я перемещаю таблицы, мне надо нажать на кнопку save, чтобы в файл json записались новые координаты всех таблиц (и старые, если я эти таблицы не перемещала).

как это выглядит

Я прописала функцию SetPositions для этого сохранения. Пока она находилась в компоненте Draggable, координаты сохранялись верно, но только одной таблицы, и кнопки сохранения были на каждой из них. Теперь кнопка одна, находится в компоненте Tables, но координаты не сохраняются и я по клику получаю результат только таблицы SomeEx7.

Какие изменения стоит внести в код, чтобы реализовать мою задачу?

Tables.js

import React, { useState, createRef, forwardRef } from "react";
import { v4 as uuidv4 } from "uuid";
import styles from "./Tables.module.css";
import tableData from "../utils/all_db_schema.json";
import { DraggableBox } from "../Draggable/Draggable";
import Xarrow, { useXarrow, Xwrapper } from "react-xarrows";
import { saveAs } from "file-saver";


export const Tables = () => {
  const [tableState, setTableState] = useState(tableData);
  const [upd, setUpd] = useState(false);
  const ref = createRef();

  var positionSettings = {
    settingsId: uuidv4(),
    tables: [
      {
        tableName: "",
        newCoordinates: {
          x: "",
          y: "",
        },
      },
    ],
  };

  function SetPositions() {
    var positions = [];
    positions.push(ref.current.getBoundingClientRect());
    //var newPosition = positions.slice(-1);
    var newX = positions.map((el) => el.x.toString());
    var newY = positions.map((el) => el.y.toString());
    tableState.map((area) => {
      area.tables.map((table) => {
        const myTable = table;
        positionSettings.tables.map((table) => {
        table.tableName = myTable.tableName.toString();
        table.newCoordinates.x = newX.join();
        table.newCoordinates.y = newY.join();
      });
      })
    })
    var settings = new Blob([JSON.stringify(positionSettings)], {
      type: "application/json",
      name: "settings",
    });
    let fileName = "settings.json";
    saveAs(settings, fileName);
  };

  return (
    <>
      {tableState.map((elem, index) => (
        <div
          className={styles.AreaContainer}
          key={index}
          style={{ color: elem.color }}
          onMouseMove={() => setUpd(!upd)}
        >
        <button onClick={() => SetPositions()}>
          save
        </button>
          {/*<p>Имя: {elem.groupName}</p>*/}
          <div className={styles.ClassesContainer}>
            {elem.tables.map((table, index) => (
              
                <DraggableBox table={table} key={index} ref={forwardRef} />
             
            ))}

            {elem.tables.map((table, index) => {
              var components = [];
              table.constraints.forEach((constrain) => {
                components.push(<Xarrow start={table.tableName} end={constrain} path={"straight"} color={"black"} strokeWidth={1} />)
              })

              return (
                components
            )
            }
            )}
          </div>
        </div>
      ))}
    </>
  );
};

Draggable.js

import Draggable from "react-draggable";
import React from "react";
import Xarrow, { useXarrow, Xwrapper } from "react-xarrows";
import { Column } from "../Column/Column";
import styles from "./Draggable.module.css";

export const DraggableBox = ({ table, key, ...style }) => {
  const { tableName, coordinates } = table;

  const updateXarrow = useXarrow();

  return (
    <Draggable onDrag={updateXarrow} onStop={updateXarrow}>
      <div
        id={tableName}
        style={{
          position: "absolute",
          left: coordinates.x + "px",
          top: coordinates.y + "px",
          border: "2px solid",
        }}
      >
        <div className={styles.TableHead}>
          <p>Название: {tableName}</p>
        </div>
        <div>
          {table.columns.map((column, index) => (
            <Column column={column} key={index} />
          ))}
        </div>
      </div>
    </Draggable>
  );
};

Пыталась также передать функцию от дочернего к родительскому, но выдает ошибку, что не видит ref

const DraggableBox = ({ table, ref, ...style }) => {
  const { tableName, coordinates } = table;
  const draggableRef = useRef();

  const updateXarrow = useXarrow();
  var positionSettings = {
    settingsId: uuidv4(),
    tables: [
      {
        tableName: "",
        newCoordinates: {
          x: "",
          y: "",
        },
      },
    ],
  };

  function SetPositions() {
    var positions = [];
    positions.push(draggableRef.current.getBoundingClientRect());
var newPosition = positions.slice(-1);
  var newX = newPosition.map((el) => el.x.toString());
  var newY = newPosition.map((el) => el.y.toString());
  positionSettings.tables.map((table) => {
    table.tableName = tableName.toString();
    table.newCoordinates.x = newX.join();
    table.newCoordinates.y = newY.join();
  });
    var settings = new Blob([JSON.stringify(positionSettings)], {
      type: "application/json",
      name: settings,
    });
    let fileName = "settings.json";
    saveAs(settings, fileName);

    //localStorage.setItem('newCoordinates', JSON.stringify(positionSettings))
    //console.log({ positionSettings });
  }; 

  useImperativeHandle(ref, () => ({ 
    SetPositions
  }))

  return (
    <Draggable onDrag={updateXarrow} onStop={updateXarrow}>
      <div
        id={tableName}
        draggableRef={draggableRef}
        style={{
          position: "absolute",
          left: coordinates.x + "px",
          top: coordinates.y + "px",
          border: "2px solid",
        }}
      >
        <div className={styles.TableHead}>
          <p>Название: {tableName}</p>
        </div>
        <div>
          {table.columns.map((column, index) => (
            <Column column={column} key={index} />
          ))}
        </div>
      </div>
    </Draggable>
  );
};

и вот родительский компонент:

export const Tables = () => {
  const [tableState, setTableState] = useState(tableData);
  const [upd, setUpd] = useState(false);
  var childRef = useRef();

  function GetPositions() {
    childRef.current?.SetPositions()
  }

  return (
    <>
      {tableState.map((elem, index) => (
        <div
          className={styles.AreaContainer}
          key={index}
          style={{ color: elem.color }}
          onMouseMove={() => setUpd(!upd)}
        >
          <button onClick={() => GetPositions()}>save</button>
          {/*<p>Имя: {elem.groupName}</p>*/}
          <div className={styles.ClassesContainer} >
            {elem.tables.map((table, index) => (
              
              <DraggableBox table={table} key={index} ref={childRef} />
            ))}

            {elem.tables.map((table, index) => {
              var components = [];
              table.constraints.forEach((constrain) => {
                components.push(
                  <Xarrow
                    start={table.tableName}
                    end={constrain}
                    path={"straight"}
                    color={"black"}
                    strokeWidth={1}
                  />
                );
              });

              return components;
            })}
          </div>
        </div>
      ))}
    </>
  );
};

Ответы (1 шт):

Автор решения: SwaD

Ниже показана возможная реализация общения дочернего компонента с родительским. Код условный и его надо адаптировать под вашу структуру данных.

Файл Tables.js

// В эту переменную положите ваши настройки таблиц
const [tablesPosition, setTablesPosition] = useState(tablePositions)
// `tablePositions` это те данные, которые вы извлекаете в рендере `elem.tables`

// Создаем функцию, которая обновляет положение таблиц при перемещении
const setNewPosition = (tableName, x, y) => {
  // Код ниже условный, т.к. вашу структуру хранения данных не знаю
  const tableState = tablesPosition.map((table) => {
    if (table.tableName === tableName) {
      table.coordiates.x = x;
      table.coordiates.y = y;
    }
    return table;
  })
  setTablesPosition(tableState);
}

// В компонент `DraggableBox` передаем функцию setNewPosition
<DraggableBox table={table} key={index} ref={childRef} changePos={setNewPosition} />

Файл Draggable.js

// Добавляем пропс к DraggableBox
const DraggableBox = ({ table, ref, changePos, ...style })
// Создаем функцию для события onStop
const updatePosition = (e) {
  setNewPosition(tableName, e.x, e.y);
  updateXarrow()
}
// Меняем функцию в событии onStop
<Draggable onDrag={updateXarrow} onStop={updatePosition}>

Теперь, после каждого передвижения таблиц, компонент будет сообщать родителю(вызывать переданную функцию), что положение изменилось. Данные о новом положении будут записаны в state компонента Tables в переменную tablesPosition.
Остается только по нажатию вашей кнопки "сохранить", взять данные из tablesPosition и сохранить их.

→ Ссылка