Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React redux implementation, fetching current price of BTC #245

Merged
merged 26 commits into from
Mar 4, 2024
Merged

Conversation

ioay
Copy link
Contributor

@ioay ioay commented Feb 13, 2024

Closes #236 #121


Initialize Redux support to store state

Show values in USD in Ledger Live dApp


What was done:

  • Added packages: react-redux, ver. ^9.1.0 | @reduxjs/toolkit", ver: ^2.2.0
  • Disabled eslint no-param-reassign rule for slice files. Redux Toolkit uses Immer under the hood to write simpler immutable update logic using "mutating" syntax.
  • Updated asyncWrapper to logPromiseFailure method to catch promises.
  • Added useCurrencyConversion hook to calculate values from BTC to USD. It should be updated to handle other currencies if appears in the redux store.
  • Added useFetchBTCPriceUSD hook to get bitcoin price from Coingecko API.
  • redux dev tools are enabled for all environments, excluding PROD env.

Preview:

Screenshot 2024-03-01 at 18 24 52

@ioay ioay requested a review from kkosiorowska February 13, 2024 15:32
@ioay ioay self-assigned this Feb 13, 2024
Copy link
Contributor

@kkosiorowska kkosiorowska left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this PR we could create our first slice and check that the state update as well as the selectors work properly, instead of just creating a slice template. As I mentioned below I think we could do it for a state of turn off/on USD value. This is a simple true/false value. What do you think?

dapp/src/redux/slices/todo.slice.ts Outdated Show resolved Hide resolved
dapp/src/redux/reducer.ts Outdated Show resolved Hide resolved
dapp/src/redux/selectors/index.ts Outdated Show resolved Hide resolved
dapp/src/redux/store.ts Outdated Show resolved Hide resolved
dapp/src/redux/slices/todo.slice.ts Outdated Show resolved Hide resolved
@ioay ioay marked this pull request as draft February 19, 2024 09:35
@ioay ioay force-pushed the react-redux branch 3 times, most recently from d2b54a5 to f09749d Compare February 20, 2024 21:03
@ioay ioay marked this pull request as ready for review February 20, 2024 23:54
@ioay
Copy link
Contributor Author

ioay commented Feb 20, 2024

PR is ready for review.

@ioay ioay requested a review from kkosiorowska February 20, 2024 23:55
@ioay ioay changed the title Added redux to dApp React redux implementation, fetching current price of BTC Feb 20, 2024
Copy link
Contributor

@kkosiorowska kkosiorowska left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left some comments. I also noticed that to close #345 we need to add support for our toggle of showing prices in USD.

dapp/src/DApp.tsx Show resolved Hide resolved
dapp/src/hooks/index.ts Outdated Show resolved Hide resolved
dapp/src/hooks/useInitApp.ts Outdated Show resolved Hide resolved
dapp/src/pages/OverviewPage/PositionDetails.tsx Outdated Show resolved Hide resolved
dapp/src/store/btc/btc.slice.ts Outdated Show resolved Hide resolved

export const fetchBTCPriceUSD = createAsyncThunk(
"btc/fetchBTCPriceUSD",
async () => fetchCryptoCurrencyPriceUSD("bitcoin"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it's a good idea to wrap it up in try catch block. We could then handle situations when we receive some error from an external server.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use asyncWrapper to handle this thunk in useInitApp.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but when something fails we do not log the error. That's why I'm wondering about it. We can also control the returned value when something fails.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's exactly what asyncWrapper does here:

useEffect(() => {
    asyncWrapper(dispatch(fetchBTCPriceUSD()))
  }, [dispatch])

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A quick (and, as ever, nonblocking) note: let's document what asyncWrapper is actually meant to do alongside the function declaration… And probably consider renaming it accordingly as well. It's extremely unclear what its purpose is right now, and reading the history of why it was added it seems like it was… Strictly to evade an eslint rule? If so, we should treat that as an anti-pattern.

If I understand correctly, what Karolina is asking here is exactly what that eslint rule that triggered adding asyncWrapper is meant to catch: if there's an error, it's just swallowed by the thunk, and with asyncWrapper it's also swallowed when it's invoked. Additionally, because we've attached a .catch in asyncWrapper, we won't get the default UnhandledPromiseRejection in the console/on error reporting endpoints like Sentry (via unhandledrejection)—and all of this escapes React's error boundary handling as well (vs e.g. using the external useErrorBoundary hook).

Copy link
Contributor Author

@ioay ioay Feb 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A quick (and, as ever, nonblocking) note: let's document what asyncWrapper is actually meant to do alongside the function declaration… And probably consider renaming it accordingly as well. It's extremely unclear what its purpose is right now, and reading the history of why it was added it seems like it was… Strictly to evade an eslint rule? If so, we should treat that as an anti-pattern.

If I understand correctly, what Karolina is asking here is exactly what that eslint rule that triggered adding asyncWrapper is meant to catch: if there's an error, it's just swallowed by the thunk, and with asyncWrapper it's also swallowed when it's invoked. Additionally, because we've attached a .catch in asyncWrapper, we won't get the default UnhandledPromiseRejection in the console/on error reporting endpoints like Sentry (via unhandledrejection)—and all of this escapes React's error boundary handling as well (vs e.g. using the external useErrorBoundary hook).

Agreed, we should deal with asyncWrapper or just find another solution to handle promises in the app, it was created to solve errors reported by eslint (#179 (comment)). Initially, we handled it separately for each Promise.

At the moment I've just added try/catch block to handle thunk error: 3f07d13

Added task to deal with it: #260

Copy link
Collaborator

@Shadowfiend Shadowfiend Feb 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fwiw I think you can get a big improvement just by making asyncWrapper something like logPromiseFailure:

/**
 * If the promise fails, log the underlying error but maintain the failed
 * promise.
 *
 * Does nothing to successful promises.
 */
function logPromiseFailure<T>(promise: Promise<T>): Promise<T> {
  return promise.catch((error) => {
    console.error(error)
    throw error
  })
}

This doesn't solve the problem of “what do we do with errors”, but it makes sure that errors aren't swallowed wholesale, makes that decision explicitly visible to anyone reading the code, and leaves you with a clear hook to plug into Sentry or some other error management system.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dapp/src/utils/externalApi.ts Outdated Show resolved Hide resolved
dapp/src/store/btc/btc.slice.ts Outdated Show resolved Hide resolved
dapp/src/store/btc/btc.slice.ts Outdated Show resolved Hide resolved
@ioay ioay requested a review from kkosiorowska February 21, 2024 19:09
Copy link
Contributor

@kkosiorowska kkosiorowska left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed this task #237. So we can close this task without it. I left a few more comments.

dapp/src/DApp.tsx Show resolved Hide resolved
dapp/src/utils/externalApi.ts Outdated Show resolved Hide resolved

export const fetchBTCPriceUSD = createAsyncThunk(
"btc/fetchBTCPriceUSD",
async () => fetchCryptoCurrencyPriceUSD("bitcoin"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but when something fails we do not log the error. That's why I'm wondering about it. We can also control the returned value when something fails.

dapp/src/store/btc/btc.slice.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@kkosiorowska kkosiorowska left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking comment
One thing wonders to me. Is there any possibility of previewing the state of redux when we open our dApp in ledger live app?

dapp/src/utils/promise.ts Show resolved Hide resolved
dapp/src/utils/externalApi.ts Outdated Show resolved Hide resolved
dapp/src/hooks/useCurrencyConversion.ts Outdated Show resolved Hide resolved
dapp/src/hooks/useCurrencyConversion.ts Outdated Show resolved Hide resolved
dapp/src/hooks/useCurrencyConversion.ts Outdated Show resolved Hide resolved
dapp/src/hooks/useCurrencyConversion.ts Outdated Show resolved Hide resolved
dapp/src/store/btc/btcThunk.ts Outdated Show resolved Hide resolved
Comment on lines +5 to +6
isLoadingPriceUSD: boolean
usdPrice: number
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking comment.

Maybe too late but I would consider using TanStack Query instead of redux at least for this feature. We can cache API response and we can set a time when the data should be re-fetched etc see https://tanstack.com/query/latest/docs/framework/react/reference/useQuery especially: staleTime and gcTime options. My point is that if we add something else to btc store we need to duplicate the logic for loading and errors indicator and we have a lot of boilerplate to create a simple feature such as fetching USD price.

We could use redux to store data that should be updated based on the emitted on-chain events but it should probably be possible with this lib as well.

Copy link
Contributor Author

@ioay ioay Feb 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first glance, it looks good, but I wonder if instead of TanStack Query we should think about using of addon to the already installed redux-toolkit package: https://redux-toolkit.js.org/tutorials/rtk-query. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good too. But I would go with TanStack Query - it looks cool and powerful, is well-documented, there are a lot of examples, and is popular and well-maintained (38k stars on GitHub, only 30 issues and 18 opened pull requests). We can achieve all the stuff we need with TanStack Query w/o redux and we can easily plugin GraphQl which will be needed once we create our subgraph for Acre network.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a task to deal with TanStack Query: #277

dapp/src/store/btc/btcThunk.ts Outdated Show resolved Hide resolved
dapp/src/api/index.ts Outdated Show resolved Hide resolved
dapp/src/hooks/useCurrencyConversion.ts Outdated Show resolved Hide resolved
dapp/src/hooks/useCurrencyConversion.ts Outdated Show resolved Hide resolved
@ioay ioay requested a review from nkuba February 29, 2024 21:48
return priceUSD
} catch (error) {
console.error(error)
return 0
Copy link
Contributor Author

@ioay ioay Mar 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After removing throw error we no longer meet typescript requirements, so I've decided to add return 0 instead of returning undefined, because if we do not retrieve the data, it is better to display the value 0.

In my opinion, in the near future, we should consider keeping certain states in localStorage or caching this data when TanStack Query will be added, and in case of an error: the previous value is displayed to the user. What do you think? @kkosiorowska @r-czajkowski @nkuba @Shadowfiend

Copy link

netlify bot commented Mar 4, 2024

‼️ Deploy request for acre-dapp-testnet rejected.

Learn more in the Netlify docs

Name Link
🔨 Latest commit fee4422

Copy link
Contributor

@kkosiorowska kkosiorowska left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we are ready to merge it. 🚀 I have only one doubt, I left in the comment. I don't want to block the PR by this. So if this part needs improvement we will fix it next one.

I have one more question. Is there any possibility of previewing the state of redux when we open our dApp in ledger live app?


return MOCK_CONVERSION_AMOUNT
}, [from.amount])
const conversionAmount = useCurrencyConversion({ from, to })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this is the correct approach in this case. 🤔 It seems to me that we should pass here only what we need. Without that useCurrencyConversion has access to other properties anyway.

Screenshot 2024-03-04 at 14 54 49

cc @r-czajkowski

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, this is a classic approach and there is nothing wrong with it as long as the object contains the necessary data of a certain type. Objects in JavaScript are passed by reference, so in terms of performance, there is nothing wrong with it either.

@ioay
Copy link
Contributor Author

ioay commented Mar 4, 2024

Actually, we are ready to merge it. 🚀 I have only one doubt, I left in the comment. I don't want to block the PR by this. So if this part needs improvement we will fix it next one.

I have one more question. Is there any possibility of previewing the state of redux when we open our dApp in ledger live app?

Good question, Ledger uses the Electron browser under the hood, so redux dev tools will not be immediately available from the console in Ledger Live. I'll create a separate story to figure out a solution.

#298

@ioay ioay merged commit 846494b into main Mar 4, 2024
16 checks passed
@ioay ioay deleted the react-redux branch March 4, 2024 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Initialize Redux support to store state
5 participants