Skip to content

Commit

Permalink
refactor(react): async action (#253)
Browse files Browse the repository at this point in the history
  • Loading branch information
tien authored Oct 16, 2024
1 parent 7f362ce commit 28ab2da
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 116 deletions.
52 changes: 52 additions & 0 deletions packages/react/src/hooks/use-async-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
type AsyncValue,
idle,
MutationError,
pending,
} from "@reactive-dot/core";
import { useCallback, useState } from "react";
import type { Observable } from "rxjs";

/**
* @internal
*/
export function useAsyncAction<
TArgs extends unknown[],
TReturn extends Promise<unknown> | Observable<unknown>,
>(action: (...args: TArgs) => TReturn) {
type Value =
TReturn extends Promise<infer Value>
? Value
: TReturn extends Observable<infer Value>
? Value
: never;

const [state, setState] = useState<AsyncValue<Value, MutationError>>(idle);

const execute = useCallback(
(...args: TArgs) => {
const resolve = (value: unknown) => setState(value as Value);

const reject = (reason?: unknown) => setState(MutationError.from(reason));

try {
setState(pending);

const result = action(...args);

if (result instanceof Promise) {
return result.then(resolve).catch(reject);
} else {
return result.subscribe({ next: resolve, error: reject });
}
} catch (error) {
const mutationError = MutationError.from(error);
setState(mutationError);
throw mutationError;
}
},
[action, setState],
);

return [state, execute] as [state: typeof state, execute: typeof execute];
}
6 changes: 0 additions & 6 deletions packages/react/src/hooks/use-async-state.ts

This file was deleted.

119 changes: 48 additions & 71 deletions packages/react/src/hooks/use-mutation.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import { SignerContext } from "../contexts/index.js";
import {
type MutationEvent,
MutationEventSubjectContext,
} from "../contexts/mutation.js";
import { MutationEventSubjectContext } from "../contexts/mutation.js";
import { typedApiAtomFamily } from "../stores/client.js";
import type { ChainHookOptions } from "./types.js";
import { useAsyncState } from "./use-async-state.js";
import { useAsyncAction } from "./use-async-action.js";
import { internal_useChainId } from "./use-chain-id.js";
import { useConfig } from "./use-config.js";
import type { ChainId, Chains } from "@reactive-dot/core";
import { MutationError, pending } from "@reactive-dot/core";
import { MutationError } from "@reactive-dot/core";
import { useAtomCallback } from "jotai/utils";
import type {
PolkadotSigner,
Transaction,
TxEvent,
TxObservable,
TypedApi,
} from "polkadot-api";
import { useCallback, useContext } from "react";
import { from } from "rxjs";
import { tap, switchMap } from "rxjs/operators";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type TxOptions<T extends Transaction<any, any, any, any>> = Parameters<
Expand Down Expand Up @@ -60,75 +58,54 @@ export function useMutation<
const mutationEventSubject = useContext(MutationEventSubjectContext);
const contextSigner = useContext(SignerContext);

const [event, setEvent] = useAsyncState<TxEvent>();
return useAsyncAction(
useAtomCallback(
useCallback(
(
get,
_set,
submitOptions?: TxOptions<ReturnType<TAction>> & {
signer?: PolkadotSigner;
},
) => {
const signer =
submitOptions?.signer ?? options?.signer ?? contextSigner;

const setState = useCallback(
(event: Omit<MutationEvent, "chainId">) => {
setEvent(event.value);
mutationEventSubject.next({ ...event, chainId });
},
[chainId, mutationEventSubject, setEvent],
);

const submit = useAtomCallback<
void,
[options?: TxOptions<ReturnType<TAction>> & { signer: PolkadotSigner }]
>(
useCallback(
async (get, _set, submitOptions) => {
const id = globalThis.crypto.randomUUID();

setState({ id, value: pending });

const signer =
submitOptions?.signer ?? options?.signer ?? contextSigner;

if (signer === undefined) {
const error = new MutationError("No signer provided");
if (signer === undefined) {
throw new MutationError("No signer provided");
}

setState({
id,
value: MutationError.from(error),
});
const id = globalThis.crypto.randomUUID();

throw error;
}
return from(get(typedApiAtomFamily({ config, chainId }))).pipe(
switchMap((typedApi) => {
const transaction = action(typedApi.tx);

const api = await get(typedApiAtomFamily({ config, chainId }));

const transaction = action(api.tx);

setState({ id, call: transaction.decodedCall, value: pending });

return new Promise((resolve, reject) =>
transaction
.signSubmitAndWatch(signer, submitOptions ?? options?.txOptions)
.subscribe({
next: (value) =>
setState({ id, call: transaction.decodedCall, value }),
error: (error) => {
setState({
id,
call: transaction.decodedCall,
value: MutationError.from(error),
});
reject(error);
},
complete: resolve,
return transaction
.signSubmitAndWatch(signer, submitOptions ?? options?.txOptions)
.pipe(
tap((value) =>
mutationEventSubject.next({
id,
chainId,
call: transaction.decodedCall,
value,
}),
),
);
}),
);
},
[
action,
chainId,
config,
contextSigner,
options?.signer,
options?.txOptions,
setState,
],
);
},
[
action,
chainId,
config,
contextSigner,
mutationEventSubject,
options?.signer,
options?.txOptions,
],
),
),
);

return [event, submit] as [event: typeof event, submit: typeof submit];
}
30 changes: 10 additions & 20 deletions packages/react/src/hooks/use-wallet-connector.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { walletsAtom } from "../stores/wallets.js";
import { useAsyncState } from "./use-async-state.js";
import { useAsyncAction } from "./use-async-action.js";
import { useConfig } from "./use-config.js";
import { MutationError, pending, connectWallet } from "@reactive-dot/core";
import { connectWallet } from "@reactive-dot/core";
import type { Wallet } from "@reactive-dot/core/wallets.js";
import { useAtomCallback } from "jotai/utils";
import { useCallback } from "react";
Expand All @@ -14,29 +14,19 @@ import { useCallback } from "react";
*/
export function useWalletConnector(wallets?: Wallet | Wallet[]) {
const hookWallets = wallets;

const config = useConfig();
const [success, setSuccess] = useAsyncState<true>();

const connect = useAtomCallback(
useCallback(
async (get, _, wallets?: Wallet | Wallet[]) => {
try {
setSuccess(pending);
return useAsyncAction(
useAtomCallback(
useCallback(
async (get, _, wallets?: Wallet | Wallet[]) => {
const walletsToConnect =
wallets ?? hookWallets ?? (await get(walletsAtom(config)));
await connectWallet(walletsToConnect);
setSuccess(true);
} catch (error) {
setSuccess(MutationError.from(error));
}
},
[config, hookWallets, setSuccess],
return true as const;
},
[config, hookWallets],
),
),
);

return [success, connect] as [
success: typeof success,
connect: typeof connect,
];
}
29 changes: 10 additions & 19 deletions packages/react/src/hooks/use-wallet-disconnector.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { walletsAtom } from "../stores/wallets.js";
import { useAsyncState } from "./use-async-state.js";
import { useAsyncAction } from "./use-async-action.js";
import { useConfig } from "./use-config.js";
import { MutationError, pending, disconnectWallet } from "@reactive-dot/core";
import { disconnectWallet } from "@reactive-dot/core";
import type { Wallet } from "@reactive-dot/core/wallets.js";
import { useAtomCallback } from "jotai/utils";
import { useCallback } from "react";
Expand All @@ -16,27 +16,18 @@ export function useWalletDisconnector(wallets?: Wallet | Wallet[]) {
const hookWallets = wallets;

const config = useConfig();
const [success, setSuccess] = useAsyncState<true>();

const disconnect = useAtomCallback(
useCallback(
async (get, _, wallets?: Wallet | Wallet[]) => {
try {
setSuccess(pending);
return useAsyncAction(
useAtomCallback(
useCallback(
async (get, _, wallets?: Wallet | Wallet[]) => {
const walletsToDisconnect =
wallets ?? hookWallets ?? (await get(walletsAtom(config)));
await disconnectWallet(walletsToDisconnect);
setSuccess(true);
} catch (error) {
setSuccess(MutationError.from(error));
}
},
[config, hookWallets, setSuccess],
return true as const;
},
[config, hookWallets],
),
),
);

return [success, disconnect] as [
success: typeof success,
disconnect: typeof disconnect,
];
}

0 comments on commit 28ab2da

Please sign in to comment.