penumbra-zone / web

Apache License 2.0
10 stars 7 forks source link

Stop using loader functions for IBC out; add selector capabilities to ZQuery #1339

Open jessepinho opened 1 week ago

jessepinho commented 1 week ago

This PR gets rid of our final loader: the IBC Out loader.

The IBC Out loader was responsible for loading and returning balances, asset metadatas, and the staking token metadata. It also was responsible for setting the initial balance selection and chain selection in the IBC Out form's Zustand state.

This PR moves things around a bit so that the IBC Out form can use Zustand entirely:

  1. Add selector functionality to ZQuery's use[Name]() hooks.
    • This mimics React Query's select property that can be passed to useQuery. Now, multiple parts of the app can access the same data in state, but reformat it into whatever shape is useful for them using a selector. Of course, whenever the data is updated in any part of the app, the update gets propagated to every part of the app.
  2. Add assets and balancesResponses to the shared Zustand slice.
    • Previously, the swap slice had swappableAssets and balancesResponses. Similarly, the send slice had transferableBalancesResponses. Now that the ibcOut slice also needs to load and use balance responses, all three features (swap, send, and IBC) all use the shared slice's instance of that data, and use selectors to filter/map/reduce/etc. as needed.
    • Furthermore, the staking slice had a stakingTokensAndFilter ZQuery object. But the data for that object can now be calculated using the balancesResponses and stakingTokenMetadata ZQuery objects in the shared slice. So I removed the stakingTokensAndFilter ZQuery object from the staking slice, and instead created a useStakingTokensAndFilter() hook that uses both shared.balancesResponses and shared.stakingTokenMetadata to create the necessary data.
  3. Introduce Zustand middlewares.

    • There are a number of places where we want to mutate the state once e.g., an API call has returned. Previously, we were doing that in the fetcher. For example, in the send slice, we'd fetch the balances responses, then set state.send.selection to the first balances response before returning the balances responses. Now that multiple minifront features are using data from a single shared slice (e.g., the send/swap/ibcOut slices all use shared.balancesResponses), we needed a way to set certain state values in those other slices in response to the shared slice updating.

      In Redux, this would be trivial — we'd just have a reducer that responds to the SET_BALANCES_RESPONSE action and updates the state accordingly. But Zustand doesn't have a notion of unique action names, so instead, we have to use middlewares that check the before/after states and determine whether a particular mutation that we care about has taken place.

      Per Zustand's author, there is no "official" way to create middleware in Zustand. Instead you just literally replace the function passed to create() with one that mutates get/set/store. So that's the approach I took here. I created several middlewares that update other pieces of state once a given piece of state updates. (For example, the sendSelectionMiddleware updates state.send.selection to the first balances response when it detects that state.shared.balancesResponses has been updated.)

  4. 🚨 New addition since I had to rebase on main: #1353 introduced the use of query params to control the swap asset in/out. Unfortunately, rebasing on that PR introduced some serious complications to this PR, so I more or less had to rewrite the setInitialAssets() function that was introduced there and use it as a middleware. That added a bit of heft to this PR, which you can see in the swapBalancesMiddleware.

For future PRs, perhaps

Closes #1243

changeset-bot[bot] commented 1 week ago

🦋 Changeset detected

Latest commit: 0db78a93745e8e56064d9e521528bc25994d3cf6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages | Name | Type | | -------------------------- | ----- | | @penumbra-zone/zquery | Major | | @penumbra-zone/getters | Minor | | minifront | Minor | | @penumbra-zone/perspective | Major | | @penumbra-zone/query | Major | | @penumbra-zone/services | Major | | @penumbra-zone/storage | Major | | @penumbra-zone/types | Major | | @repo/ui | Patch | | node-status | Patch | | @penumbra-zone/crypto-web | Major | | @penumbra-zone/wasm | Major |

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

jessepinho commented 3 days ago

Moving back to draft because of merge conflicts that will require a bit of manual resolution

jessepinho commented 1 hour ago

OK @penumbra-zone/web-reviewers — could you please review this now? It got hung up on a rebase that was pretty hefty, and since this has gotten pretty big, it'd be super helpful to get a review soon so I can merge it ASAP. Thanks!