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

refactor: AddressDetailExplorer to fn component #317

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from

Conversation

tuliomir
Copy link
Contributor

@tuliomir tuliomir commented Sep 13, 2024

Acceptance Criteria

  • Refactors the AddressDetailExplorer to functional component

Motivation

This component used the deprecated withRouter connector, which will become unavailable in the coming upgrade to React Router v6.

In order to remove its usage, it was necessary to refactor it to use react hooks as a functional component. This proved to be harder than initially foreseen because the code relied heavily upon async state setters, a feature that is also no longer available with useState() hooks.

So, instead of setting the state, waiting for it to be rendered and then applying the next set of instructions, the code now sets the state and then a useEffect() handles its change. Many data flow reorganizations were made to achieve this new structure.

Note for the reviewers

Since this component is so complex and had deep changes to its data flow, I'd recommend checking the before and after versions of the code to understand the changes made.

Security Checklist

  • Make sure you do not include new dependencies in the project unless strictly necessary and do not include dev-dependencies as production ones. More dependencies increase the possibility of one of them being hijacked and affecting us.

@tuliomir tuliomir self-assigned this Sep 13, 2024
@tuliomir tuliomir mentioned this pull request Sep 17, 2024
1 task
*/
function shouldUpdate(tx, checkToken) {
const arr = [...tx.outputs, ...tx.inputs];
const { token: queryToken } = pagination.current.obtainQueryParams();
Copy link
Member

Choose a reason for hiding this comment

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

question(if-minor): Should we ignore token?

Let's say a tx arrives for a token that is not the currently selected.
When we change the selected token to this token, will the results reflect the current state or a previous state that should be refreshed?

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 actually have some logic holes in this component. The way we're calling shouldUpdate(), with checkToken: false, ensures that shouldUpdate will always return false. So we never show the alert anyway.

However, to answer your question directly, when we change the token the onTokenSelectChanged method is called which basically resets the whole page, so the results would always be correct.

*
* @return {boolean} True if should update the list, false otherwise
*/
function shouldUpdate(tx, checkToken) {
Copy link
Member

Choose a reason for hiding this comment

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

suggestion(if-minor): Move this method outside the useEffect.

If should also change to receive the address, this will make the contents of the useEffect cleaner.

Copy link
Contributor Author

@tuliomir tuliomir Oct 15, 2024

Choose a reason for hiding this comment

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

Good point, this refactoring clarification also made a bug stand out ( see comment above ).

* Will discard any errors and always set the metadataLoaded to true, even without any data.
* @type {(function(*): Promise<void>)|*}
*/
const getSelectedTokenMetadata = useCallback(async tokenToFetch => {
Copy link
Member

Choose a reason for hiding this comment

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

useCallback is meant to cache a function definition between re-renders.
Since this function has no dependencies, why do we need the useCallback?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function is indirectly used by an effect:

  • getSelectedTokenMetadata
  • reloadTokenSummaryAndHistory
  • reloadTokensForAddress
  • useEffect on line 321

To be able to use a function within an effect, we need to define it as a callback. I believe we could restructure this component in an even better way, but I didn't want to build additional components for the purpose of this refactor only.

This PR is my proposal between a full restructure and the simplest refactor.

});
return;
}
const getHistoryData = useCallback(
Copy link
Member

Choose a reason for hiding this comment

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

This callback requires txCache but it is the only one to call setTxCache.
Since this updates the txCache it I think we should not use setTxCache and just update the txCache directly.

The idea would be that the txCache is never "set", meaning that the same object will be used between re-renders.
This method adds transactions to the object and the next re-render will have the same object.
What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

State immutability is an important concept for React. By always using set and never mutating the object we can benefit from some features like "time machine" when debugging our application.

However, when studying some other alternative impelmentations for this getHistoryData based on your comment, I found a bug that could erase some transactions when under race conditions.

Fixed it on f979f47

setLoadingSummary(false);
return undefined;
},
[getHistoryData, getSelectedTokenMetadata]
Copy link
Member

Choose a reason for hiding this comment

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

Both of these dependencies can be a simple function, if they are changed to be a funcion maybe this should too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

getHistoryData() gets called independently of getSelectedTokenMetadata() within onNextPageClicked() and onPreviousPageClicked().

I think that's why both these functions are separated: there's no need to make an additional metadata call on page change.

alexruzenhack
alexruzenhack previously approved these changes Sep 17, 2024
src/components/AddressDetailExplorer.js Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: In Review (WIP)
Development

Successfully merging this pull request may close these issues.

3 participants