react-use#usePrevious TypeScript Examples

The following examples show how to use react-use#usePrevious. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: useCacheRender.ts    From UUI with MIT License 6 votes vote down vote up
export function useValueCacheRender<T>(data: T, render: (data: T) => React.ReactNode, options: { comparator?: (previous: T, current: T) => boolean }) {
  const [rendered, setRendered] = useState(render(data))
  const previous = usePrevious<T>(data) as T
  const current = data

  useEffect(() => {
    const isSame = options?.comparator ? options.comparator(previous, current) : isEqual(previous, current)
    if (!isSame) {
      setRendered(render(current))
    }
  }, [previous, current, options, render])

  return rendered
}
Example #2
Source File: TabsOfTruth.tsx    From mStable-apps with GNU Lesser General Public License v3.0 6 votes vote down vote up
TabsOfTruth: FC<
  State & {
    setActiveIndex: (index: number) => void
    className?: string
  }
> = ({ tabs, setActiveIndex, activeTabIndex, className }) => {
  const container = useRef<HTMLDivElement>(undefined as never)
  const prevActiveTabIndex = usePrevious(activeTabIndex)
  const [activePos, setActivePos] = useState<[number, number]>([0, 0])

  useLayoutEffect(() => {
    const { offsetWidth, offsetLeft } = container.current.childNodes.item(activeTabIndex + 1) as HTMLElement
    setActivePos([offsetLeft, offsetWidth])
  }, [activeTabIndex])

  return (
    <TabsContainer ref={container} className={className}>
      <Transition in={activeTabIndex !== prevActiveTabIndex} appear timeout={250} unmountOnExit={false}>
        {className => <ActiveTab pos={activePos} className={className} />}
      </Transition>
      {tabs.map(({ id, title, active }, index) => (
        <TabButton
          active={active}
          key={id}
          onClick={() => {
            setActiveIndex(index)
          }}
        >
          {title}
        </TabButton>
      ))}
    </TabsContainer>
  )
}
Example #3
Source File: useCacheRender.ts    From UUI with MIT License 5 votes vote down vote up
export function useArrayCacheRender<T>(
  data: T[],
  render: (data: T) => React.ReactNode,
  options: {
    id: (i: T) => string;
    comparator?: (previous: T, current: T) => boolean;
  },
) {
  const [list, setList] = useState<{
    id: string;
    rendered: React.ReactNode;
  }[]>([])
  const previous = usePrevious<T[]>(data) as T[]
  const current = data

  useEffect(() => {
    const isSameOne = (i: T, j: T) => options.id(i) === options.id(j)
    const intersected = intersectionWith(previous, current, isSameOne)
    const removing = xorWith(previous, intersected, isSameOne)
    const adding = xorWith(current, intersected, isSameOne)
    const updating = intersected.filter((i) => {
      const p = previous.find((j) => options.id(i) === options.id(j))
      const c = current.find((j) => options.id(i) === options.id(j))
      if (!p) return false
      if (!c) return false

      return options.comparator ? options.comparator(c, p) : !isEqual(c, p)
    })

    const newList = clone(list)

    for (const i of removing) {
      remove(newList, (r) => r.id === options.id(i))
    }
    for (const i of updating) {
      const index = list.findIndex((r) => r.id === options.id(i))
      const c = current.find((c) => options.id(c) === options.id(i))
      if (index > -1 && c) newList[index] = { id: options.id(c), rendered: render(c) }
    }
    for (const i of adding) {
      newList.push({ id: options.id(i), rendered: render(i) })
    }

    setList(newList)
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [previous, current])

  const rendered = useMemo(() => {
    return list.map((i) => i.rendered)
  }, [list])

  return rendered
}
Example #4
Source File: ClientConnectionSettingsForm.tsx    From flood with GNU General Public License v3.0 5 votes vote down vote up
ClientConnectionSettingsForm: FC<ClientConnectionSettingsFormProps> = ({
  onSettingsChange,
}: ClientConnectionSettingsFormProps) => {
  const {i18n} = useLingui();
  const [selectedClient, setSelectedClient] = useState<ClientConnectionSettings['client']>(DEFAULT_SELECTION);

  const prevSelectedClient = usePrevious(selectedClient);

  if (selectedClient !== prevSelectedClient) {
    onSettingsChange(null);
  }

  let settingsForm: ReactNode = null;
  switch (selectedClient) {
    case 'Deluge':
      settingsForm = <DelugeConnectionSettingsForm onSettingsChange={onSettingsChange} />;
      break;
    case 'qBittorrent':
      settingsForm = <QBittorrentConnectionSettingsForm onSettingsChange={onSettingsChange} />;
      break;
    case 'rTorrent':
      settingsForm = <RTorrentConnectionSettingsForm onSettingsChange={onSettingsChange} />;
      break;
    case 'Transmission':
      settingsForm = <TransmissionConnectionSettingsForm onSettingsChange={onSettingsChange} />;
      break;
    default:
      break;
  }

  return (
    <div>
      <FormRow>
        <Select
          id="client"
          label={i18n._('connection.settings.client.select')}
          onSelect={(newSelectedClient) => {
            setSelectedClient(newSelectedClient as ClientConnectionSettings['client']);
          }}
          defaultID={DEFAULT_SELECTION}
        >
          {SUPPORTED_CLIENTS.map((client) => (
            <SelectItem key={client} id={client}>
              <Trans id={`connection.settings.${client.toLowerCase()}`} />
            </SelectItem>
          ))}
        </Select>
      </FormRow>
      {settingsForm}
    </div>
  );
}
Example #5
Source File: AccountProvider.tsx    From mStable-apps with GNU Lesser General Public License v3.0 5 votes vote down vote up
OnboardConnection: FC = ({ children }) => {
  const network = useNetwork()

  const [chainId, setChainId] = useChainIdCtx()
  const [injectedChainId] = useInjectedChainIdCtx()
  const previousInjectedChainId = usePrevious(injectedChainId)

  const jsonRpcProviders = useJsonRpcProviders()
  const [injectedProvider] = useInjectedProviderCtx()
  const [, setSigners] = useSignerCtx()

  useEffect(() => {
    if (chainId) localStorage.setItem('mostRecentChainId', chainId as unknown as string)
  }, [chainId])

  const injectedMismatching = injectedChainId !== chainId

  useEffect(() => {
    if (!chainId || !injectedChainId) return

    // Change chainId when injectedChainId changes and doesn't match chainId
    if (injectedMismatching && previousInjectedChainId) {
      setChainId(injectedChainId)
    }
  }, [chainId, injectedChainId, injectedMismatching, previousInjectedChainId, setChainId])

  useEffect(() => {
    if (!injectedProvider || !previousInjectedChainId) return

    const method = network.isMetaMaskDefault ? 'wallet_switchEthereumChain' : 'wallet_addEthereumChain'
    const data = [
      {
        chainId: utils.hexStripZeros(utils.hexlify(network.chainId)),
        ...(!network.isMetaMaskDefault && {
          chainName: `${network.protocolName} (${network.chainName})`,
          nativeCurrency: network.nativeToken,
          rpcUrls: network.rpcEndpoints,
          blockExplorerUrls: [network.getExplorerUrl()],
        }),
      },
    ]

    injectedProvider.send(method, data).catch(error => {
      console.warn(error)
    })
  }, [injectedProvider, network, previousInjectedChainId])

  useEffect(() => {
    if (!jsonRpcProviders) return setSigners(undefined)

    if (injectedMismatching) {
      return setSigners({
        provider: jsonRpcProviders.provider as never,
      })
    }

    setSigners({
      provider: injectedProvider ?? (jsonRpcProviders.provider as never),
      signer: injectedProvider?.getSigner() as never,
    })
  }, [injectedMismatching, injectedProvider, jsonRpcProviders, setSigners])

  // Remount Onboard when the chainId changes
  // Necessitated by Onboard's design and internal state
  return (
    <OnboardProvider key={chainId} chainId={chainId ?? ChainIds.EthereumMainnet}>
      {children}
    </OnboardProvider>
  )
}
Example #6
Source File: transactionsUpdater.ts    From mStable-apps with GNU Lesser General Public License v3.0 5 votes vote down vote up
TransactionsUpdater = (): null => {
  const account = useAccount()
  const accountPrev = usePrevious(account)
  const provider = useSignerOrProvider()
  const blockNumber = useBlockNow()

  const state = useTransactionsState()
  const { check, finalize, reset } = useTransactionsDispatch()

  /**
   * Reset transactions state on account change
   */
  useEffect(() => {
    if (accountPrev !== account) {
      reset()
    }
  }, [account, accountPrev, reset])

  /**
   * Check pending transaction status on new blocks, and finalize if possible.
   */
  useEffect(
    (): (() => void) | void => {
      if (provider && blockNumber) {
        let stale = false
        Object.values(state)
          .filter(tx => STATUS_NEEDS_CHECK.includes(tx.status) && tx.hash && tx.blockNumber !== blockNumber)
          .forEach(tx => {
            ;(((provider as Signer).provider || provider) as Provider)
              .getTransactionReceipt(tx.hash as string)
              .then((receipt: TransactionReceipt) => {
                if (!stale) {
                  if (!receipt) {
                    if (tx?.manifest?.id) {
                      check(tx.manifest.id, blockNumber)
                    }
                  } else {
                    finalize(tx.manifest, receipt)
                  }
                }
              })
              .catch(() => {
                if (tx?.manifest?.id) {
                  check(tx.manifest.id, blockNumber)
                }
              })
          })

        return () => {
          stale = true
        }
      }
      return undefined
    },
    // `blockNumber` and `provider` should be the only deps; otherwise it will
    // check too often.
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [blockNumber, provider],
  )

  return null
}
Example #7
Source File: useBigDecimalInput.ts    From mStable-apps with GNU Lesser General Public License v3.0 5 votes vote down vote up
useBigDecimalInput = (
  initialValue?: BigDecimal | string,
  options?: number | { min?: number; max?: number; decimals?: number },
): [
  BigDecimal | undefined,
  string | undefined,
  (formValue: (string | null) | (string | undefined)) => void,
  (amount?: BigDecimal) => void,
] => {
  const decimals = (typeof options === 'number' ? options : options?.decimals) ?? 18
  const prevDecimals = usePrevious(decimals)

  const [value, setValue] = useState<BigDecimal | undefined>(
    initialValue instanceof BigDecimal ? initialValue : BigDecimal.maybeParse(initialValue, decimals),
  )

  const [formValue, setFormValue] = useState<string | undefined>(value?.simple !== 0 ? value?.string : undefined)

  const onChange = useCallback(
    _formValue => {
      const amount = BigDecimal.maybeParse(_formValue, decimals)

      if (
        amount &&
        typeof options === 'object' &&
        ((options.min && amount.simple < options.min) || (options.max && amount.simple > options.max))
      ) {
        // Ignore inputs outside parameters
        return
      }

      setFormValue(_formValue ?? undefined)
      setValue(BigDecimal.maybeParse(_formValue, decimals))
    },
    [decimals, options],
  )

  useEffect(() => {
    if (decimals !== prevDecimals) {
      setValue(BigDecimal.maybeParse(formValue, decimals))
    }
  }, [decimals, formValue, prevDecimals])

  return [value, formValue, onChange, setValue]
}
Example #8
Source File: Form.tsx    From yugong with MIT License 4 votes vote down vote up
Form: React.FC<FormProps> = (props) => {
  const {
    registersFunction,
    eventDispatch,
    classes,
    moduleId,
    api
  } = props;
  const { setRunningTimes } = useDispatch<Dispatch>().runningTimes;
  const [formColumns, setformColumns] = useState<AnyObjectType[]>([]);
  const [resetBtn, setResetBtn] = useState<string>();
  const [subBtn, setSubBtn] = useState<string>();
  const [configArgs, setConfigArgs] = useState<[ArgumentsMixed, ArgumentsString, ArgumentsString]>()

  // 数据编译到显示端
  const formColumnsCompiler = useCallback(
    (formColumns: AnyObjectType[]) => {
      formColumns.forEach(column => {
        Object.keys(column).forEach(key => {
          let element = column[key];
          if (isType(element, 'String')) {
            getResult(element);
          };
        })
      });
      setformColumns(formColumns);
    },
    [],
  )

  const setForm = useCallback(
    (...args: [ArgumentsMixed, ArgumentsString, ArgumentsString] ) => {
      const [formColumns, resetText, submitText] = args;
      setConfigArgs(args);
      const columns = getArgumentsItem(formColumns);
      const reset = getArgumentsItem(resetText);
      setResetBtn(reset as string)
      const submit = getArgumentsItem(submitText);
      setSubBtn(submit as string)
      if (isType(columns, "Array")) {
        formColumnsCompiler(columns as AnyObjectType[])
      } else {
        message.error('表单数据不正确')
      }
    },
    [formColumnsCompiler],
  )

  const resetForm = useCallback(
    () => {
      if (configArgs) {
        const [formColumns, resetText, submitText] = configArgs;
        setForm(formColumns, resetText, submitText);
      }
    },
    [configArgs, setForm],
  )

  const prevFormColumns = usePrevious<any>(formColumns);

  // First setup registers
  useEffect(() => {
    registersFunction({
      setForm,
      resetForm
    })
  }, [setForm, registersFunction, resetForm])

  // Second, distributing events
  useEffect(() => {
    eventDispatch().mount()
    return () => {
      eventDispatch().unmount();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  const onSubmit = useCallback(
    async (values) => {
      console.log('values', values);
      
      setRunningTimes({
        [`form_${moduleId}`]: values
      });
      const apiArguments = api?.find((item) => item.apiId === 'submit');
      if (apiArguments) {
        apiArguments.body = [{ type: 'mixed', fieldName: 'formdata', data: values }];
        await requester(apiArguments || {});
      }
      eventDispatch().submit();
    },
    [api, eventDispatch, moduleId, setRunningTimes],
  )
  
  // 得到一个初始值
  useEffect(() => {
    const formColumns = config.exposeFunctions![0].arguments![0] as ArgumentsMixed;
    const resetText = config.exposeFunctions![0].arguments![1] as ArgumentsString;
    const submitText = config.exposeFunctions![0].arguments![2] as ArgumentsString;
    setForm(formColumns, resetText, submitText);
  }, [setForm])

  // 数据比较更新
  const [updateKey, setUpdateKey] = useState(Date.now());
  useEffect(() => {
    formColumns.some((item: SubItemValue, index) => {
      if (item.initialValue === prevFormColumns?.[index]?.initialValue) {
        setUpdateKey(Date.now())
        return true
      }
      return false
    })
  }, [prevFormColumns, formColumns])

  const handleOnFocus = useCallback(
    e => {
      if(e.target.parentNode.className.indexOf('ant-picker-input') >= 0 && isMobile){
        e.target.setAttribute('readonly', true);
      }
    },
    [],
  )
  
  return (
    <Wrapper {...props} maxWidth>
      <div className={s.wrap}>
        <BetaSchemaForm<DataItem>
          key={updateKey}
          className={classNames(s.form, classes.form)}
          shouldUpdate={false}
          layoutType="Form"
          onFinish={onSubmit}
          columns={formColumns}
          autoFocusFirstInput={false}
          onFocus={handleOnFocus}
          submitter={{
            // 配置按钮文本
            searchConfig: {
              resetText: resetBtn || '重置',
              submitText: subBtn || '提交',
            },
            submitButtonProps: {
              className: classes.submit,
            },
            resetButtonProps: {
              className: classes.reset
            }
          }}
        />
      </div>
    </Wrapper>
  )
}
Example #9
Source File: useEstimatedOutput.ts    From mStable-apps with GNU Lesser General Public License v3.0 4 votes vote down vote up
useEstimatedOutput = (
  inputValue?: BigDecimalInputValue,
  outputValue?: BigDecimalInputValue,
  lpPriceAdjustment?: LPPriceAdjustment,
  shouldSkip?: boolean,
): Output => {
  const inputValuePrev = usePrevious(inputValue)
  const outputValuePrev = usePrevious(outputValue)

  const [estimatedOutputRange, setEstimatedOutputRange] = useFetchState<[BigDecimal, BigDecimal]>()

  const [action, setAction] = useState<Action | undefined>()

  const signer = useSigner()
  const massetState = useSelectedMassetState() as MassetState
  const { address: massetAddress, fAssets, bAssets, feeRate: swapFeeRate, redemptionFeeRate } = massetState

  const poolAddress = Object.values(fAssets).find(
    f =>
      f.feederPoolAddress === inputValue?.address ||
      f.feederPoolAddress === outputValue?.address ||
      f.address === inputValue?.address ||
      f.address === outputValue?.address,
  )?.feederPoolAddress

  const inputRatios = useMassetInputRatios()
  const scaledInput = useScaledInput(inputValue, outputValue, inputRatios)

  const contract: Contract | undefined = useMemo(() => {
    if (!signer) return

    // use feeder pool to do swap
    if (poolAddress) {
      return FeederPool__factory.connect(poolAddress, signer)
    }
    return Masset__factory.connect(massetAddress, signer)
  }, [poolAddress, massetAddress, signer])

  const isFeederPool = contract?.address === poolAddress

  const exchangeRate = useMemo<FetchState<number>>(() => {
    if (shouldSkip) return {}

    if (estimatedOutputRange.fetching) return { fetching: true }
    if (!scaledInput?.high || !outputValue || !estimatedOutputRange.value) return {}

    const [, high] = estimatedOutputRange.value

    if (!high.exact.gt(0) || !scaledInput.high.exact.gt(0)) {
      return { error: 'Amount must be greater than zero' }
    }

    const value = high.simple / scaledInput.high.simple
    return { value }
  }, [estimatedOutputRange, scaledInput, outputValue, shouldSkip])

  const feeRate = useMemo<FetchState<BigDecimal>>(() => {
    if (shouldSkip || !withFee.has(action) || !estimatedOutputRange.value?.[1]) return {}

    if (estimatedOutputRange.fetching) return { fetching: true }

    const _feeRate = action === Action.SWAP ? swapFeeRate : redemptionFeeRate

    const swapFee = estimatedOutputRange.value[1]
      .scale()
      .divPrecisely(BigDecimal.ONE.sub(_feeRate))
      .sub(estimatedOutputRange.value[1].scale())

    return { value: swapFee }
  }, [action, estimatedOutputRange, swapFeeRate, redemptionFeeRate, shouldSkip])

  const priceImpact = useMemo<FetchState<PriceImpact>>(() => {
    if (estimatedOutputRange.fetching || !estimatedOutputRange.value) return { fetching: true }

    if (!scaledInput || !scaledInput.high.exact.gt(0)) return {}

    const value = getPriceImpact([scaledInput.scaledLow, scaledInput.scaledHigh], estimatedOutputRange.value, lpPriceAdjustment)

    return { value }
  }, [estimatedOutputRange.fetching, estimatedOutputRange.value, lpPriceAdjustment, scaledInput])

  /*
   * |------------------------------------------------------|
   * | ROUTES                                               |
   * | -----------------------------------------------------|
   * | Input  | Output | Function      | Tokens             |
   * | -----------------------------------------------------|
   * | basset | masset | masset mint   | 1 basset, 1 masset |
   * | masset | basset | masset redeem | 1 masset, 1 basset |
   * | basset | basset | masset swap   | 2 bassets          |
   * | fasset | basset | fpool swap    | 1 fasset           |
   * | fasset | masset | fpool swap    | 1 fasset           |
   * |------------------------------------------------------|
   */

  const inputEq = inputValuesAreEqual(inputValue, inputValuePrev)
  const outputEq = inputValuesAreEqual(outputValue, outputValuePrev)
  const eq = inputEq && outputEq

  const [update] = useDebounce(
    () => {
      if (!scaledInput || !outputValue || shouldSkip || !contract) return

      const { address: inputAddress, decimals: inputDecimals } = inputValue
      const { address: outputAddress, decimals: outputDecimals } = outputValue

      const isLPRedeem = contract.address === inputAddress
      const isLPMint = contract.address === outputAddress
      const isMassetMint = bAssets[inputAddress]?.address && outputAddress === massetAddress
      const isBassetSwap = [inputAddress, outputAddress].filter(address => bAssets[address]?.address).length === 2
      const isInvalid = inputAddress === outputAddress

      if (!scaledInput.high.exact.gt(0)) return

      // same -> same; fallback to input value 1:1
      if (isInvalid) {
        setEstimatedOutputRange.value([scaledInput.scaledLow, scaledInput.high])
        return
      }

      let outputLowPromise: Promise<BigNumber> | undefined
      let outputHighPromise: Promise<BigNumber> | undefined

      if (isMassetMint || isLPMint) {
        setAction(Action.MINT)
        outputLowPromise = contract.getMintOutput(inputAddress, scaledInput.low.scale(inputDecimals).exact)
        outputHighPromise = contract.getMintOutput(inputAddress, scaledInput.high.exact)
      } else if ((isFeederPool || isBassetSwap) && !isLPRedeem) {
        setAction(Action.SWAP)
        outputLowPromise = contract.getSwapOutput(inputAddress, outputAddress, scaledInput.low.scale(inputDecimals).exact)
        outputHighPromise = contract.getSwapOutput(inputAddress, outputAddress, scaledInput.high.exact)
      } else if (!isFeederPool || isLPRedeem) {
        setAction(Action.REDEEM)
        outputLowPromise = contract.getRedeemOutput(outputAddress, scaledInput.low.scale(inputDecimals).exact)
        outputHighPromise = contract.getRedeemOutput(outputAddress, scaledInput.high.exact)
      }

      if (outputLowPromise && outputHighPromise) {
        setEstimatedOutputRange.fetching()

        Promise.all([outputLowPromise, outputHighPromise])
          .then(data => {
            const [_low, _high] = data
            const low = new BigDecimal(_low, outputDecimals)
            const high = new BigDecimal(_high, outputDecimals)
            setEstimatedOutputRange.value([low, high])
          })
          .catch(_error => {
            setEstimatedOutputRange.error(sanitizeMassetError(_error))
          })
        return
      }

      setEstimatedOutputRange.value()
    },
    2500,
    [eq],
  )

  useEffect(() => {
    if (shouldSkip) return

    if (!eq && contract && scaledInput && outputValue) {
      if (scaledInput.high.exact.gt(0)) {
        setEstimatedOutputRange.fetching()
        update()
      } else {
        setEstimatedOutputRange.value()
      }
    }
  }, [eq, contract, setEstimatedOutputRange, update, scaledInput, outputValue, shouldSkip])

  return useMemo(
    () => ({
      estimatedOutputAmount: {
        fetching: estimatedOutputRange.fetching,
        error: estimatedOutputRange.error,
        value: estimatedOutputRange.value?.[1],
      },
      priceImpact,
      exchangeRate,
      feeRate,
    }),
    [estimatedOutputRange, priceImpact, exchangeRate, feeRate],
  )
}
Example #10
Source File: ApolloProvider.tsx    From mStable-apps with GNU Lesser General Public License v3.0 4 votes vote down vote up
ApolloProvider: FC = ({ children }) => {
  const addErrorNotification = useAddErrorNotification()
  const [persisted, setPersisted] = useState(false)
  const network = useNetwork()
  const previousChainId = usePrevious(network.chainId)
  const networkChanged = previousChainId && network.chainId !== previousChainId

  // Serialized array of failed endpoints to be excluded from the client
  const [failedEndpoints] = useState<string>('')

  const handleError = useCallback(
    (message: string, error?: unknown): void => {
      console.error(message, error)

      // Not significant at the moment; falls back to the hosted service
      if (message.includes('Exhausted list of indexers')) return

      let sanitizedError: string = message
      let body: string | undefined
      if (message.includes('Failed to query subgraph deployment')) {
        sanitizedError = `Subgraph: ${message.split(': ')[1] ?? message}`
      }

      if ((error as { operation?: Operation })?.operation?.operationName) {
        body = `Subgraph: ${(error as { operation: Operation }).operation.operationName}`
      }

      addErrorNotification(sanitizedError, body)
    },
    [addErrorNotification],
  )

  useEffect(() => {
    Promise.all(
      Object.keys(caches).map(clientName =>
        persistCache({
          cache: caches[clientName as keyof ApolloClients] as never,
          storage: window.localStorage as never,
          key: `${network.chainId}-${clientName}`,
        }),
      ),
    )
      .catch(_error => {
        console.warn('Cache persist error', _error)
      })
      .finally(() => {
        setPersisted(true)
      })
  }, [setPersisted, network.chainId])

  const apollo = useMemo<{ ready: true; clients: ApolloClients } | { ready: false }>(() => {
    if (!persisted) return { ready: false }

    // const _failedEndpoints = failedEndpoints.split(',')

    const errorLink = onError(error => {
      const { networkError, graphQLErrors } = error
      if (graphQLErrors) {
        graphQLErrors.forEach(({ message, ..._error }) => {
          // if (_failedEndpoints.includes(ctx.uri)) return

          handleError(message, error)

          // On any GraphQL error, mark the endpoint as failed; this may be
          // excessive, but failed endpoints are merely deprioritised rather than
          // excluded completely.
          // _failedEndpoints.push(ctx.uri)
        })
      }

      if (networkError) {
        handleError(networkError.message, error)
      }
      // setFailedEndpoints(_failedEndpoints.join(','))
    })

    const retryIf = (error: { statusCode: number }) => {
      const doNotRetryCodes = [500, 400]
      return !!error && !doNotRetryCodes.includes(error.statusCode)
    }

    const clients = (Object.keys(caches) as AllGqlEndpoints[])
      .map<[AllGqlEndpoints, ApolloClient<NormalizedCacheObject>]>(name => {
        if (!Object.prototype.hasOwnProperty.call(network.gqlEndpoints, name)) {
          return [name, dummyClient]
        }

        const endpoints = network.gqlEndpoints[name as keyof typeof network['gqlEndpoints']]
        const preferred = endpoints.filter(endpoint => !failedEndpoints.split(',').includes(endpoint))[0]
        const fallback = endpoints[0] // There is always a fallback, even if it failed
        const endpoint = preferred ?? fallback
        const timeoutLink = new ApolloLinkTimeout(30000)

        const endpointNameLink = new ApolloLink((operation, forward) => {
          operation.extensions.endpointName = name
          return forward(operation)
        })
        const httpLink = new HttpLink({ uri: endpoint })
        const retryLink = new RetryLink({ delay: { initial: 1e3, max: 5e3, jitter: true }, attempts: { max: 1, retryIf } })
        const link = ApolloLink.from([endpointNameLink, networkStatusLink, retryLink, timeoutLink, errorLink, httpLink])
        const client = new ApolloClient<NormalizedCacheObject>({
          cache: caches[name],
          link,
          defaultOptions: {
            watchQuery: {
              fetchPolicy: 'cache-and-network',
              errorPolicy: 'all',
            },
            query: {
              fetchPolicy: 'cache-first',
              errorPolicy: 'all',
            },
          },
        })

        return [name, client]
      })
      .reduce<ApolloClients>(
        (prev, [clientName, client]) => ({ ...prev, [clientName as keyof ApolloClients]: client }),
        {} as ApolloClients,
      )

    return { ready: true, clients }
  }, [persisted, failedEndpoints, handleError, network])

  useEffect(() => {
    // Reset caches that can have conflicting keyFields on network change
    // This prevents cached data from a previously selected network being used
    // on a newly-selected network
    if (networkChanged && (apollo as { clients: ApolloClients }).clients) {
      ;(apollo as { clients: ApolloClients }).clients.blocks.resetStore().catch(error => {
        console.error(error)
      })
    }
  }, [apollo, networkChanged])

  return apollo.ready ? <apolloClientsCtx.Provider value={apollo.clients}>{children}</apolloClientsCtx.Provider> : <Skeleton />
}
Example #11
Source File: tokenSubscriptionsUpdater.ts    From mStable-apps with GNU Lesser General Public License v3.0 4 votes vote down vote up
TokenSubscriptionsUpdater = (): null => {
  const { reset, updateBalances, updateAllowances } = useTokensDispatch()
  const signer = useSigner()
  const [chainId] = useChainIdCtx()
  const prevChainId = usePrevious(chainId)

  const account = useAccount()
  const prevAccount = usePrevious(account)
  const blockNumber = useBlockNow()

  const balanceSubscriptionsSerialized = useBalanceSubscriptionsSerialized()
  const allowanceSubscriptionsSerialized = useAllowanceSubscriptionsSerialized()

  // Clear all contracts and tokens if the account/chain changes.
  useEffect(() => {
    if (prevAccount !== account || chainId !== prevChainId) {
      reset()
    }
  }, [account, chainId, prevAccount, prevChainId, reset])

  useEffect(() => {
    if (!account || !signer || !signer.provider || chainId !== prevChainId) return

    const allowanceSubs: {
      address: string
      spenders: string[]
      decimals: number
    }[] = JSON.parse(allowanceSubscriptionsSerialized)

    const allowancePromises = allowanceSubs.flatMap(({ address, spenders, decimals }) =>
      spenders.map(async spender => {
        const data = await (signer.provider as Provider).call({
          to: address,
          data: contractInterface.encodeFunctionData('allowance', [account, spender]),
        })
        const allowance = contractInterface.decodeFunctionResult('allowance', data)
        return {
          address,
          spender,
          allowance: new BigDecimal(allowance[0], decimals),
        }
      }),
    )

    Promise.all(allowancePromises)
      .then(allowances => {
        updateAllowances(
          allowances.reduce<Parameters<typeof updateAllowances>[0]>(
            (_allowances, { address, allowance, spender }) => ({
              ..._allowances,
              [address]: { ..._allowances[address], [spender]: allowance },
            }),
            {},
          ),
        )
      })
      .catch(console.error)
  }, [account, allowanceSubscriptionsSerialized, blockNumber, chainId, prevChainId, signer, updateAllowances])

  useEffect(() => {
    if (!account || !signer || !signer.provider || chainId !== prevChainId) return

    const balanceSubs: { address: string; decimals: number }[] = JSON.parse(balanceSubscriptionsSerialized)

    const balancePromises = balanceSubs
      .filter(({ address }) => address !== constants.AddressZero)
      .map(async ({ address, decimals }) => {
        const data = await (signer.provider as Provider).call({
          to: address,
          data: contractInterface.encodeFunctionData('balanceOf', [account]),
        })
        const balance = contractInterface.decodeFunctionResult('balanceOf', data)
        return [address, new BigDecimal(balance[0], decimals)]
      })

    Promise.all(balancePromises)
      .then(balances => {
        updateBalances(Object.fromEntries(balances))
      })
      .catch(console.error)
  }, [account, balanceSubscriptionsSerialized, blockNumber, chainId, prevChainId, signer, updateBalances])

  useEffect(() => {
    if (account && signer?.provider) {
      signer.provider.getBalance(account).then(_balance => {
        updateBalances({
          [constants.AddressZero as string]: new BigDecimal(_balance),
        })
      })
    } else {
      updateBalances({
        [constants.AddressZero as string]: BigDecimal.ZERO,
      })
    }
  }, [blockNumber, account, signer, updateBalances])

  return null
}