@msquared/etherbase-client
Version:
React hooks for interacting with Etherbase smart contracts
102 lines (101 loc) • 3.97 kB
JavaScript
"use client";
import { useEffect, useMemo, useRef, useState } from "react";
import { useEtherbaseContext } from "../EtherbaseProvider";
import { getConfig } from "../config";
import { WebSocketManager } from "./WebSocketManager";
import useEtherbaseSource from "./useEtherbaseSource";
function deepMerge(target, source) {
if (!target) {
return source;
}
if (!source) {
return target || {};
}
const output = { ...target };
for (const key in source) {
console.log("key", key, source[key], target[key]);
if (source[key] === null) {
delete output[key];
}
else if (source[key] instanceof Object && key in target) {
output[key] = deepMerge(target[key], source[key]);
}
else {
output[key] = source[key];
}
}
return output;
}
export default function useEtherstore({ contractAddress, path, options = {}, onStateChange, }) {
useEtherbaseContext();
const [state, setState] = useState({});
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const keyRef = useRef(Math.random().toString(36).substring(2, 9));
if (!contractAddress) {
throw new Error("Contract address must be provided in path");
}
// biome-ignore lint/correctness/useExhaustiveDependencies: need a stable reference
const statePath = useMemo(() => path, [JSON.stringify(path)]);
// biome-ignore lint/correctness/useExhaustiveDependencies: need a stable reference
const optionsMemo = useMemo(() => options, [JSON.stringify(options)]);
const { useBackend } = getConfig();
const { setValue } = useEtherbaseSource({ sourceAddress: contractAddress });
// state is the new state to be set. todo implement merge false
const update = async (state, merge = true) => {
// todo check if the contract is a source
// if (!isSource(contractAddress)) {
// throw new Error(`Contract ${contractAddress} is not registered as an source`);
// }
if (!useBackend) {
await setValue(state);
return;
}
console.log("Updating state", contractAddress, state);
try {
await WebSocketManager.get().setValue({
contractAddress,
state,
});
}
catch (err) {
setError(err instanceof Error ? err.message : String(err));
throw err;
}
};
useEffect(() => {
async function createSubscription() {
try {
await WebSocketManager.get().addStateSubscription(keyRef.current, {
contractAddress: contractAddress,
statePath: statePath,
options: optionsMemo,
onStateUpdate: (update) => {
setLoading(false);
// deep merge the update into the state
// @ts-ignore
setState((prev) => {
const mergedState = deepMerge(prev, update.state);
if (onStateChange) {
onStateChange(mergedState);
}
return mergedState;
});
},
});
}
catch (err) {
console.error("Error creating subscription", err);
setError(err instanceof Error ? err.message : String(err));
setLoading(false);
return;
}
}
console.log("Creating subscription", contractAddress, statePath);
createSubscription();
return () => {
WebSocketManager.get().removeSubscription(keyRef.current);
};
}, [contractAddress, statePath, optionsMemo, onStateChange]);
return { state, loading, error, update };
}