-
Notifications
You must be signed in to change notification settings - Fork 465
Conversation
@@ -0,0 +1,27 @@ | |||
import { Dispatch } from 'redux'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wasn't sure the right place for this file, open to suggestions
|
||
export interface LatestErrorComponentProps { | ||
assetData?: string; | ||
latestError?: any; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per convo w/ @fragosti , we explored two options:
-
When error is "cleared" (after timeout), set
currentError
to be undefined. This would have been nice because it's the most straightforward representation of the current state, but animating the un-mounting of the component would either be pretty hairy or involve introducing a new dependency. -
Keep the error around in
latestError
but setlatestErrorDismissed
totrue
, and then render the sliding down error message. I don't love keeping the last error around but treating it as "dismissed", but it makes the animation logic simple.
After discussing, I went with option 2 for the drawbacks listed in option 1. Open to feedback though.
packages/instant/src/redux/store.ts
Outdated
@@ -3,4 +3,5 @@ import { createStore, Store as ReduxStore } from 'redux'; | |||
|
|||
import { reducer, State } from './reducer'; | |||
|
|||
export const store: ReduxStore<State> = createStore(reducer); | |||
const reduxDevTools = (window as any).__REDUX_DEVTOOLS_EXTENSION__; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For using redux dev tools in chrome. I couldn't find a pattern for this in the website code, so went with this (window as any)
hack
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the website its setup so that it's only being connected in development https://github.com/zalmoxisus/redux-devtools-extension, however I think this is fine?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like using the package and having it be dev only, fixed in 3f0d7c70bd156edc4039d08737e74e99c0b3a04e
packages/instant/src/redux/store.ts
Outdated
@@ -3,4 +3,5 @@ import { createStore, Store as ReduxStore } from 'redux'; | |||
|
|||
import { reducer, State } from './reducer'; | |||
|
|||
export const store: ReduxStore<State> = createStore(reducer); | |||
const reduxDevTools = (window as any).__REDUX_DEVTOOLS_EXTENSION__; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the website its setup so that it's only being connected in development https://github.com/zalmoxisus/redux-devtools-extension, however I think this is fine?
selectedAssetData?: string; | ||
selectedAssetAmount?: BigNumber; | ||
selectedAssetBuyState: AsyncProcessState; | ||
ethUsdPrice?: BigNumber; | ||
latestBuyQuote?: BuyQuote; | ||
} | ||
interface StateWithError extends BaseState { | ||
latestError: any; | ||
latestErrorDismissed: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needs to follow boolean naming convention (ie. "was", "is", "should" etc...)
|
||
// TODO: tests for this | ||
export const bestNameForAsset = (assetData: string | undefined, defaultString: string) => { | ||
if (assetData === undefined) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we use _.isUndefined
pretty consistently for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah I noticed that, but I try to avoid importing a library function if it's simple, easy and readable to perfrom the same thing without it, a la https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea I kind of agree? Don't feel too strongly. However, as mentioned previously, I am a big fan of consistency and reducing decisions. It's nice to not have to think about "how am I going to check for undefined". And it's also nice for the entire codebase to do it the same way. Idk, I think since we already have lodash in the bundle it's worth doing that, but don't feel strongly about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think since we already have lodash in the bundle it's worth doing that
If we are able to get our bundle optimized, the bundle should only include the lodash functions that we use, and it'd be nice to be pulling in one less function if we consistently used this approach for checking for undefined
That being said, I hear you RE: consistency and reducing decisions. For the sake of those two points, I'm down to use the lodash function for now. We can revisit our approach to lodash in general as a team later, and perhaps establish a new consistent pattern moving forward at that point.
@@ -12,6 +13,9 @@ export interface ZeroExInstantContainerProps {} | |||
|
|||
export const ZeroExInstantContainer: React.StatelessComponent<ZeroExInstantContainerProps> = props => ( | |||
<Container width="350px"> | |||
<Container zIndex={1} position="relative"> | |||
<LatestError /> | |||
</Container> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔥
const slidingDirection = props.latestErrorDismissed ? 'down' : 'up'; | ||
const { icon, message } = errorDescription(props.latestError, props.assetData); | ||
return <SlidingError direction={slidingDirection} icon={icon} message={message} />; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A convention I'm trying to follow is to not have any Component code in these container files, they simply connect a pure presentational component to the redux store and do any state -> prop mapping. Seems like you don't need this component to me?
- The sliding direction logjc can just be done in mapStateToProps.
- icon is a pure function of state.
- message is a pure function of state.
The only thing is not rendering when latestError
is not defined, but do we need that? The error hides behind container.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of getting rid of this component, but I think we need the custom "not render when latestError is undefined" logic. Some thoughts:
- If there is no latestError, we wouldn't have anything to send in to
SlidingError
'sicon
andmessage
props. I like keeping these props as required instead of optional. - Even if we rendered
SlidingError
withouticon
andmessage
props, in the current implementation, rendering it in the "Slide Down" phase would have it pop up at the top and slide down, which is not what we want. We could modifySlidingError
to return a blank div if icon and message are empty, but that doesn't feel quite right to me.
I may be missing something though, let me know if there is an alternative approach in which we could get rid of this component.
If you agree we need to keep the component, I understand the convention and pattern of separating presentational and container components, but in this case it seems overboard IMHO: this component is extremely simple and it's highly unlikely it'd be used anywhere else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could change it so that latestError
sticks around and you simply toggle it up and down in the flasher / redux. This kind of seems better to me because then it becomes impossible to make the error just disappear?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per slack convo, decided to leave as-is and address later if needed
selectedAssetData?: string; | ||
selectedAssetAmount?: BigNumber; | ||
selectedAssetBuyState: AsyncProcessState; | ||
ethUsdPrice?: BigNumber; | ||
latestBuyQuote?: BuyQuote; | ||
} | ||
interface StateWithError extends BaseState { | ||
latestError: any; | ||
latestErrorDismissed: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if we call this latestErrorState
and it can be either Present
or Hidden
or something. And lets not allow it to be undefined
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this -- feels more clear than undefined or a boolean 💯. Will update!
icon: '😢', | ||
message: bestMessage || 'Something went wrong...', | ||
}; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would perhaps create one util/error.ts
file that has one error
or errorUtil
object export and also provides the flashNewError
utility?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll try this and see how it feels once implemented. ErrorFlasher
is kinda big though, not sure if I love it being grouped together with other things
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in d45ffb33513057789a2e1cf03a9f01ee05846e17
import { assetMetaData } from '../data/asset_meta_data'; | ||
|
||
// TODO: tests for this | ||
export const bestNameForAsset = (assetData: string | undefined, defaultString: string) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the way this is structured but I'd like to follow our somewhat soft convention of exporting one thing from file.
So the api would be assetDataUtils.bestName()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this convention could be bad for optimizing builds. Not sure. Cool w/ keeping convention for now though
latestError: undefined; | ||
latestErrorDismissed: undefined; | ||
} | ||
export type State = StateWithError | StateWithoutError; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we had two types, BaseState
that looks like above and ErrorState
that looks like this:
interface ErrorState {
latestError?: any;
latestErrorDismissed?: boolean;
}
the final type is:
type State = BaseState & ErrorState
BaseState
could even be broken down further
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea -- but I think with the change to Present | Hidden
type which is always present might not be as important
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good overall. Feel free to address issues how you see fit :)
const slidingDirection = props.latestErrorDismissed ? 'down' : 'up'; | ||
const { icon, message } = errorDescription(props.latestError, props.assetData); | ||
return <SlidingError direction={slidingDirection} icon={icon} message={message} />; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could change it so that latestError
sticks around and you simply toggle it up and down in the flasher / redux. This kind of seems better to me because then it becomes impossible to make the error just disappear?
…index to be set on container
2d7eb9a
to
7b43cd1
Compare
@@ -12,20 +13,45 @@ export interface InstantHeadingProps { | |||
selectedAssetAmount?: BigNumber; | |||
totalEthBaseAmount?: BigNumber; | |||
ethUsdPrice?: BigNumber; | |||
quoteState: AsyncProcessState; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think this should be called quoteRequestState
. quote
is data, and it doesn't make sense to me for it to have state.
@@ -47,11 +73,11 @@ export const InstantHeading: React.StatelessComponent<InstantHeadingProps> = pro | |||
<Flex direction="column" justify="space-between"> | |||
<Container marginBottom="5px"> | |||
<Text fontSize="16px" fontColor={ColorOption.white} fontWeight={500}> | |||
{displaytotalEthBaseAmount(props)} | |||
{loadingOrAmount(props.quoteState, displaytotalEthBaseAmount(props))} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels kind of weird to have two nested Text
components. Can't displayTotalEthUsdAmount
take care of all of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call -- yes, will fix
|
||
if (!_.isUndefined(value) && !_.isUndefined(assetData)) { | ||
// even if it's debounced, give them the illusion it's loading | ||
dispatch(actions.updateBuyQuoteStatePending()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah good idea
} | ||
|
||
export const actions = { | ||
updateEthUsdPrice: (price?: BigNumber) => createAction(ActionTypes.UPDATE_ETH_USD_PRICE, price), | ||
updateSelectedAssetAmount: (amount?: BigNumber) => createAction(ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT, amount), | ||
updateSelectedAssetBuyState: (buyState: AsyncProcessState) => | ||
updatebuyOrderState: (buyState: AsyncProcessState) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this is not camelCase
@@ -25,12 +25,22 @@ export enum ActionTypes { | |||
UPDATE_SELECTED_ASSET_AMOUNT = 'UPDATE_SELECTED_ASSET_AMOUNT', | |||
UPDATE_SELECTED_ASSET_BUY_STATE = 'UPDATE_SELECTED_ASSET_BUY_STATE', | |||
UPDATE_LATEST_BUY_QUOTE = 'UPDATE_LATEST_BUY_QUOTE', | |||
UPDATE_BUY_QUOTE_STATE_PENDING = 'UPDATE_BUY_QUOTE_STATE_PENDING', | |||
UPDATE_BUY_QUOTE_STATE_FAILURE = 'UPDATE_BUY_QUOTE_STATE_FAILURE', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SET_BUY_QUOTE_STATE_PENDING
& SET_BUY_QUOTE_STATE_FAILURE
? Update means it takes an argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, wasn't hip to this convention. Will change.
@@ -7,21 +7,31 @@ import { AsyncProcessState } from '../types'; | |||
|
|||
import { Action, ActionTypes } from './actions'; | |||
|
|||
export enum LatestErrorDisplay { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should go in types.ts
and can probably be more general.
|
||
export const errorUtil = { | ||
errorFlasher: new ErrorFlasher(), | ||
errorDescription: (error?: any, assetData?: string): { icon: string; message: string } => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this!
Description
Shows error messages for AssetBuyer
getBuyQuoteAsync
errorsAlso:
adds quoteState to keep track of the status of the current quote
shows "--" if we get an error when fetching in top right
renamed selectedAssetBuyState to buyOrderState
handling of additional AssetBuyer errors
tests for
bestNameForAsset
polish
Testing instructions
Types of changes
Checklist:
[WIP]
if necessary.[sol-cov] Fixed bug
.