InitNewElement = ({ nodekeys, savingState, setSavingState }) => {
  const [saveTimeout, setSaveTimeout] = useState(false);
  const savedData = useRef(false);
  const { actions, query, connectors } = useEditor((state, query) => {
    return {};
  });
  const options = useContext(optionsCtx);
  const doSave = (query, keepalive) => {
    if (!query.serialize) return;

    const data = craftToSaltcorn(
      JSON.parse(query.serialize()),
      "ROOT",
      options
    );
    const urlroot = options.page_id ? "pageedit" : "viewedit";
    if (savedData.current === false) {
      //do not save on first call
      savedData.current = JSON.stringify(data.layout);

      return;
    }
    if (isEqual(savedData.current, JSON.stringify(data.layout))) return;
    savedData.current = JSON.stringify(data.layout);
    setSavingState({ isSaving: true });

    fetch(`/${urlroot}/savebuilder/${options.page_id || options.view_id}`, {
      method: "POST", // or 'PUT'
      keepalive,//this is conditional bec body size is limited to 64KB
      headers: {
        "Content-Type": "application/json",
        "CSRF-Token": options.csrfToken,
      },
      body: JSON.stringify(data),
    })
      .then((response) => {
        response.json().then((data) => {
          if (typeof data?.error === "string") {
            // don't log duplicates
            if (!savingState.error)
              window.notifyAlert({ type: "danger", text: data.error });
            setSavingState({ isSaving: false, error: data.error });
          } else setSavingState({ isSaving: false });
        });
      })
      .catch((e) => {
        const text =
          e.message === "Failed to fetch"
            ? "Network connection lost"
            : e || "Unable to save";
        // don't log duplicates
        if (savingState.error) setSavingState({ isSaving: false, error: text });
        else {
          window.notifyAlert({ type: "danger", text: text });
          setSavingState({
            isSaving: false,
            error: text,
          });
        }
      });
  };
  useEffect(() => {
    window.addEventListener("beforeunload", () => doSave(query, true));
    window.addEventListener("blur", () => doSave(query));
    window.addEventListener("pagehide", () => doSave(query));
  }, []);
  const throttledSave = useThrottle(() => {
    doSave(query);
  });
  const onNodesChange = (arg, arg1) => {
    const nodes = arg.getSerializedNodes();
    const newNodeIds = [];
    Object.keys(nodes).forEach((id) => {
      if (!nodekeys.current.includes(id)) {
        newNodeIds.push(id);
      }
    });
    nodekeys.current = Object.keys(nodes);
    if (newNodeIds.length === 1) {
      const id = newNodeIds[0];
      const node = nodes[id];
      if (node.displayName === "LibraryElem") {
        const layout = node.props.layout;
        layoutToNodes(
          layout.layout ? layout.layout : layout,
          query,
          actions,
          node.parent,
          options
        );
        setTimeout(() => {
          actions.delete(id);
        }, 0);
      } else if (node.displayName !== "Column") {
        actions.selectNode(id);
      }
    }

    throttledSave();
  };
  useEffect(() => {
    const nodes = query.getSerializedNodes();
    nodekeys.current = Object.keys(nodes);
    actions.setOptions((options) => {
      const oldf = options.onNodesChange(
        (options.onNodesChange = oldf
          ? (q) => {
              oldf(q);
              onNodesChange(q);
            }
          : onNodesChange)
      );
    });
  }, []);

  return [];
}
@param {object} props
@param {object} props.nodekeys
@returns {object[]}
@category saltcorn-builder
@subcategory components
@namespace