From f13c330ec061d42ab4ff7eaf6c2c2ce1fdd3a4f6 Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Tue, 13 Aug 2024 10:44:27 -0400 Subject: [PATCH] refactor: react-core docs, cleanup --- .changeset/dry-windows-decide.md | 6 + .changeset/long-pens-deliver.md | 10 ++ libs/design-system/src/app/AppLogin.tsx | 23 ++-- libs/hostd-react/src/api.ts | 6 +- libs/react-core/README.md | 108 +++++++++++++++++- libs/react-core/src/coreProvider.tsx | 3 + libs/react-core/src/index.ts | 1 - libs/react-core/src/useAppSettings/index.tsx | 8 +- libs/react-core/src/workflows.tsx | 5 + libs/renterd-react/src/bus.ts | 6 +- .../src/blockHeight.ts} | 0 libs/units/src/index.ts | 1 + libs/walletd-react/src/api.ts | 6 +- 13 files changed, 161 insertions(+), 22 deletions(-) create mode 100644 .changeset/dry-windows-decide.md create mode 100644 .changeset/long-pens-deliver.md rename libs/{react-core/src/blockHeight.tsx => units/src/blockHeight.ts} (100%) diff --git a/.changeset/dry-windows-decide.md b/.changeset/dry-windows-decide.md new file mode 100644 index 000000000..43061d116 --- /dev/null +++ b/.changeset/dry-windows-decide.md @@ -0,0 +1,6 @@ +--- +'@siafoundation/design-system': minor +'@siafoundation/react-core': minor +--- + +App setting allowCustomApi is now more accurately named loginWithCustomApi. diff --git a/.changeset/long-pens-deliver.md b/.changeset/long-pens-deliver.md new file mode 100644 index 000000000..4428e8b45 --- /dev/null +++ b/.changeset/long-pens-deliver.md @@ -0,0 +1,10 @@ +--- +'@siafoundation/hostd-react': minor +'@siafoundation/react-core': minor +'@siafoundation/renterd-react': minor +'@siafoundation/units': minor +'@siafoundation/walletd-react': minor +--- + +The network block height calculation methods have been moved to the units +package. diff --git a/libs/design-system/src/app/AppLogin.tsx b/libs/design-system/src/app/AppLogin.tsx index dd9d0010e..c25c07142 100644 --- a/libs/design-system/src/app/AppLogin.tsx +++ b/libs/design-system/src/app/AppLogin.tsx @@ -26,9 +26,9 @@ function getDefaultValues(api: string) { } function getFields({ - allowCustomApi, + loginWithCustomApi, }: { - allowCustomApi: boolean + loginWithCustomApi: boolean }): ConfigFields, never> { return { api: { @@ -37,7 +37,8 @@ function getFields({ placeholder: 'http://127.0.0.1:9980', validation: { validate: { - required: (value) => !allowCustomApi || !!value || 'API is required', + required: (value) => + !loginWithCustomApi || !!value || 'API is required', url: (value) => { try { const url = new URL(value) @@ -123,7 +124,7 @@ type Props = { export function AppLogin({ appName, route, routes }: Props) { const router = usePagesRouter() const { settings, setSettings } = useAppSettings() - const { allowCustomApi } = settings + const { loginWithCustomApi } = settings const defaultValues = useMemo( () => getDefaultValues(settings.api), @@ -139,12 +140,12 @@ export function AppLogin({ appName, route, routes }: Props) { useEffect(() => { form.clearErrors() // eslint-disable-next-line react-hooks/exhaustive-deps - }, [allowCustomApi]) + }, [loginWithCustomApi]) const onValid = useCallback( async (values: typeof defaultValues) => { let api = '' - if (allowCustomApi) { + if (loginWithCustomApi) { const url = new URL(values.api) api = `${url.protocol}//${url.host}` } @@ -171,7 +172,7 @@ export function AppLogin({ appName, route, routes }: Props) { } }, [ - allowCustomApi, + loginWithCustomApi, form, router, routes, @@ -182,7 +183,7 @@ export function AppLogin({ appName, route, routes }: Props) { ] ) - const fields = getFields({ allowCustomApi }) + const fields = getFields({ loginWithCustomApi }) const onInvalid = useOnInvalid(fields) const error = form.formState.errors.api || form.formState.errors.password @@ -211,18 +212,18 @@ export function AppLogin({ appName, route, routes }: Props) { setSettings({ - allowCustomApi: !allowCustomApi, + loginWithCustomApi: !loginWithCustomApi, }) } > - {allowCustomApi ? 'Hide custom API' : 'Show custom API'} + {loginWithCustomApi ? 'Hide custom API' : 'Show custom API'}
- {allowCustomApi ? ( + {loginWithCustomApi ? ( +) { + return useGetSwr({ ...args, route: '/contracts/:id' }) +} +``` + +The resulting hook is then used like any other swr hook, the return value is an +`SWRResponse` where data is of type `ConsensusStateResponse` and error is of +type `SWRError`. + +```ts +const { data, isValidating, error } = useContract({ + params: { id: '123', extra: 'abc' }, +}) +``` + +The hook must be called with any required params. Params that are specified in +the route string are automatically replaced with the provided values, any others +are added to the query string. The above hook would make a request to +`/contracts/123?extra=abc`. + +#### Configure swr and axios + +`useGetSwr` and its siblings all accept a config object which can be used to +configure swr and axios. For example, to refresh the data every 10 seconds and +dedupe the request: + +```ts +useGetSwr({ + route: '/contracts/:id', + config: { + swr: { + refreshInterval: 10_000, + dedupingInterval: 10_000, + }, + }, +}) +``` + +To use an abort signal with a long-running request: + +```ts +useGetSwr({ + route: '/contracts/:id', + config: { + axios: { + signal: controller.signal, + }, + }, +}) +``` + +### Imperative mutations + +Declarative hooks are great for fetching data, but sometimes you need a method +that can be used more imperatively, for example when submitting a form. + +`usePostFunc` and its siblings are used for imperative mutations. Instead of +declaratively fetching, the following hook returns a method that can be called. +Besides the `HookArgsCallback` and route string, the method takes another +callback argument which provides access to a `mutate` function. The callback is +triggered any time the contract add method is called successfully. The `mutate` +function can be used to trigger revalidation on dependent keys to refresh data +that is affected by `useContractAdd`, such as the contracts list. + +```ts +export function useContractAdd( + args?: HookArgsCallback< + ContractsAddParams, + ContractsAddPayload, + ContractsAddResponse + > +) { + return usePostFunc({ ...args, route: busContractIdNewRoute }, async (mutate) => { + mutate((key) => { + return key.startsWith(busContractRoute) + }) + }) +} + +const addContract = useContractAdd() + +function handleSubmit(values: Values) { + const { status, data, error } = await addContract({ + params: { id: '123', extra: 'abc' }, + payload: { + contract: contractRevision + startHeight: 40000 + totalCost: '2424242421223232' + } + }) +} +``` diff --git a/libs/react-core/src/coreProvider.tsx b/libs/react-core/src/coreProvider.tsx index 2a0884556..3de6c841b 100644 --- a/libs/react-core/src/coreProvider.tsx +++ b/libs/react-core/src/coreProvider.tsx @@ -9,6 +9,9 @@ type Props = { children: React.ReactNode } +/** + * The core provider includes the workflows provider and the swr config provider. + */ export function CoreProvider({ fallback, cacheProvider, children }: Props) { return ( diff --git a/libs/react-core/src/index.ts b/libs/react-core/src/index.ts index bdc615be7..484dd77fd 100644 --- a/libs/react-core/src/index.ts +++ b/libs/react-core/src/index.ts @@ -9,7 +9,6 @@ export * from './useGetDownload' export * from './useAppSettings/currency' export * from './useAppSettings' export * from './useTryUntil' -export * from './blockHeight' export * from './userPrefersReducedMotion' export * from './mutate' diff --git a/libs/react-core/src/useAppSettings/index.tsx b/libs/react-core/src/useAppSettings/index.tsx index 23a5d696a..c34a987f4 100644 --- a/libs/react-core/src/useAppSettings/index.tsx +++ b/libs/react-core/src/useAppSettings/index.tsx @@ -14,7 +14,7 @@ export type CurrencyDisplay = 'sc' | 'fiat' | 'bothPreferSc' | 'bothPreferFiat' export type AppSettings = { api: string - allowCustomApi: boolean + loginWithCustomApi: boolean siaCentral: boolean password?: string currency: CurrencyOption @@ -30,7 +30,7 @@ export type AppSettings = { const defaultSettings: AppSettings = { api: '', - allowCustomApi: false, + loginWithCustomApi: false, siaCentral: true, password: undefined, currency: currencyOptions[0], @@ -172,6 +172,10 @@ function useAppSettingsMain({ type State = ReturnType const SettingsContext = createContext({} as State) +/** + * The app settings context allows you to configure all app settings and + * preferences such as the api address, password, currency, etc. + */ export const useAppSettings = () => useContext(SettingsContext) export function AppSettingsProvider({ children, ...props }: Props) { diff --git a/libs/react-core/src/workflows.tsx b/libs/react-core/src/workflows.tsx index 23012125c..96860fbbc 100644 --- a/libs/react-core/src/workflows.tsx +++ b/libs/react-core/src/workflows.tsx @@ -61,6 +61,11 @@ function useWorkflowsMain() { type State = ReturnType const WorkflowsContext = createContext({} as State) +/** + * The workflows context is a generic way to mark and track any long-running as in-progress. + * For example, all mutation hooks (eg: usePostFunc) automatically are automatically tracked + * as workflows using their unique route key. + */ export const useWorkflows = () => useContext(WorkflowsContext) type Props = { diff --git a/libs/renterd-react/src/bus.ts b/libs/renterd-react/src/bus.ts index 99300a5fd..320ce3be1 100644 --- a/libs/renterd-react/src/bus.ts +++ b/libs/renterd-react/src/bus.ts @@ -8,10 +8,12 @@ import { HookArgsSwr, HookArgsCallback, HookArgsWithPayloadSwr, - getMainnetBlockHeight, - getTestnetZenBlockHeight, delay, } from '@siafoundation/react-core' +import { + getMainnetBlockHeight, + getTestnetZenBlockHeight, +} from '@siafoundation/units' import { AccountResetDriftParams, AccountResetDriftPayload, diff --git a/libs/react-core/src/blockHeight.tsx b/libs/units/src/blockHeight.ts similarity index 100% rename from libs/react-core/src/blockHeight.tsx rename to libs/units/src/blockHeight.ts diff --git a/libs/units/src/index.ts b/libs/units/src/index.ts index 138440db2..feef7932b 100644 --- a/libs/units/src/index.ts +++ b/libs/units/src/index.ts @@ -3,5 +3,6 @@ export * from './address' export * from './humanUnits' export * from './currency' export * from './blockTime' +export * from './blockHeight' export * from './bytes' export * from './valuePer' diff --git a/libs/walletd-react/src/api.ts b/libs/walletd-react/src/api.ts index d3de584ee..b66303de9 100644 --- a/libs/walletd-react/src/api.ts +++ b/libs/walletd-react/src/api.ts @@ -6,10 +6,12 @@ import { HookArgsSwr, HookArgsCallback, delay, - getMainnetBlockHeight, - getTestnetZenBlockHeight, useDeleteFunc, } from '@siafoundation/react-core' +import { + getMainnetBlockHeight, + getTestnetZenBlockHeight, +} from '@siafoundation/units' import { ConsensusNetworkParams, ConsensusNetworkResponse,