-
Notifications
You must be signed in to change notification settings - Fork 35
feat(pricefeed) Migrate Price Feed IDs to documentation #493
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
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
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 you can get rid of mui entirely here; everything I see you doing with it is trivial to do with tailwind, or there are already built-in components in nextra or in the app that you can use instead.
Additionally, please clean up the state management for the asset types list as per my comment. Make sure to properly handle loading and error statesat least, and ideally also follow some of the best practices I shared in that comment.
components/PriceFeedTable.tsx
Outdated
MenuItem, | ||
Box, | ||
Snackbar, | ||
} from "@mui/material"; |
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.
Mentioned this in slack but we should try to use built-in nextra components where possible. I don't really have an issue with using mui for stuff that isn't in nextra, but I also don't think you get much out of the stuff you're using here and can probably do away with mui entirely.
package.json
Outdated
@@ -16,8 +16,12 @@ | |||
"@codesandbox/sandpack-react": "^2.6.1", | |||
"@cookbookdev/docsbot": "^4.21.5", | |||
"@cosmjs/cosmwasm-stargate": "^0.31.0", | |||
"@emotion/react": "^11.13.3", | |||
"@emotion/styled": "^11.13.0", |
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 don't see anywhere that you're actually using emotion, is this a peer dependency of mui or something?
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.
https://stackoverflow.com/a/69632381
Yes it is.
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.
That stackoverflow just tells me that they're using emotion, not that it's a peer dependency. There shouldn't be any reason to add emotion to our dependencies unless if either:
- you need to use it directly (in general you shouldn't, we have tailwind for this, the only possible exception would be if you need to pass overrides to a library that internally uses it)
- it's a peer dependency of a package you depend on (what this means is that the package has it listed in it's
peerDependencies
list in thepackage.json
, indicating that the dependency must be installed by the consumer; typically this only happens for things like plugins defining what they are plugging into or things like React where there can only be one copy of the dependency in the build graph)
}; | ||
|
||
fetchPriceFeeds(); | ||
}, []); |
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.
There's a few issues with this effect and the state it creates:
- It's not accurate to initialize
priceFeeds
to an empty array on line 24, you should properly handle loading states and represent that state differently from a loaded array. - You aren't actually using
priceFeeds
anywhere except to get the list of asset types. You should extract the asset types and dedupe them when you load the data and store that in the state, rather than deduping in the render path on line 76 below. - The transformation on lines 33 - 37 is IMO not useful; you're basically just changing snake case to camel case. IMO there isn't any serious benefit to doing that, and there are costs (added cognitive overhead, makes the code harder to search, etc). I know that camel case is the convention, but sticking to convention is not worth added complexity and making the code harder to search.
- You should always use a
catch
any time you call a promise without usingawait
, e.g. on line 41. Omitting thecatch
means uncaught exceptions could potentially put the app into an unstable state; additionally here you should make the UI indicate errors properly. - I always strongly recommend extracting functions like
fetchPriceFeeds
outside the closure scope. For one, it makes the code simpler and easier to follow; for two it's easy to introduce performance issues by creating closures in a hot path by accident. - You should always prefer
??
over||
(on lines 35 and 36), see https://typescript-eslint.io/rules/prefer-nullish-coalescing/. - Minor nit but in es6 you can do
[...new Set(...)]
instead ofArray.from(new Set(...))
to dedupe, the former is considered more idiomatic. - You can use the "fake sum types" pattern I shared to encapsulate the loading/error state management here if you like.
Putting all this together you can change the component to something like this:
export const PriceFeedTable = () => {
const [selectedAssetType, setSelectedAssetType] = useState("All");
// ...
<AssetTypesSelect
selectedAssetType={selectedAssetType}
setSelectedAssetType={setSelectedAssetType}
/>
}
type AssetTypesSelectProps = {
selectedAssetType: string;
setSelectedAssetType: (value: string) => void;
}
const AssetTypesSelect = ({ selectedAssetType, setSelectedAssetType }: AssetTypesSelectProps) => {
const assetTypes = useAssetTypes();
switch (state.type) {
case AssetTypesStateType.NotLoaded:
case AssetTypesStateType.Loading: {
// use any spinner we're already using in the app anywhere else...
return <Spinner />
}
case AssetTypesStateType.Error: {
return <p>Error loading asset types!</p>;
}
case AssetTypesStateType.Loaded: {
return (
<Select
value={selectedAssetType}
onChange={(e) => setSelectedAssetType(e.target.value)}
size="small"
sx={{ width: 200 }}
>
<MenuItem value="All">All Asset Types</MenuItem>
{state.assetTypes.map(assetType => (
<MenuItem key={assetType} value={assetType}>
{assetType ?? "Uncategorized"}
</MenuItem>
))}
</Select>
);
}
}
};
const useAssetTypes = (): AssetTypesState => {
const [assetTypes, setAssetTypes] = useState<AssetTypesState>(AssetTypesState.NotLoaded());
useEffect(() => {
setAssetTypes(AssetTypesState.Loading());
fetchAssetTypes()
.then(assetTypes => setAssetTypes(AssetTypesState.Loaded(assetTypes)))
.catch(error => setAssetTypes(AssetTypesState.Error(error)));
}, []);
return assetTypes;
}
const fetchAssetTypes = async () => {
const hermesClient = new HermesClient("https://hermes.pyth.network");
const feeds = await hermesClient.getPriceFeeds();
return [...new Set(feeds.map(feed => feed.attributes.asset_type ?? ""))];
};
enum AssetTypesStateType {
NotLoaded,
Loading,
Loaded,
Error
}
const AssetTypesState = {
NotLoaded: () => ({ type: AssetTypesStateType.NotLoaded as const }),
Loading: () => ({ type: AssetTypesStateType.Loading as const }),
Loaded: (assetTypes: string[]) => ({ type: AssetTypesStateType.Loaded as const, assetTypes }),
Error: (error: unknown) => ({ type: AssetTypesStateType.Error as const, error }),
}
type AssetTypesState = typeof AssetTypesState[keyof typeof AssetTypesState];
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.
Replying to point 3.
I am using PriceFeeds variable to filter here
const matchesSearch =
feed.id.toLowerCase().includes(searchTerm.toLowerCase())
and then to display the data. What should be the best way around?
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, apologies, I was so focused on the asset types that I didn't think about the other usage of the feeds. In my suggested code, just replace fetchAssetTypes
with:
const fetchPriceFeeds = async () => {
const priceFeeds = await new HermesClient("https://hermes.pyth.network").getPriceFeeds();
const assetTypes = [...new Set(priceFeeds.map(feed => feed.attributes.asset_type ?? ""))];
return { priceFeeds, assetTypes };
};
Then update variable names etc as appropriate to reflect that the state is storing the price feeds and not just asset types (e.g. rename useAssetTypes
to usePriceFeeds
). Then update the component to e.g.:
export const PriceFeedTable = () => {
const [selectedAssetType, setSelectedAssetType] = useState("All");
const priceFeedsState = usePriceFeeds();
// ...
<AssetTypesSelect
priceFeedsState={priceFeedsState}
selectedAssetType={selectedAssetType}
setSelectedAssetType={setSelectedAssetType}
/>
}
type AssetTypesSelectProps = {
priceFeedsState: PriceFeedsState;
selectedAssetType: string;
setSelectedAssetType: (value: string) => void;
}
const AssetTypesSelect = ({ priceFeedsState, selectedAssetType, setSelectedAssetType }: AssetTypesSelectProps) => {
switch (priceFeedsState.type) {
// ...
}
}
Let me know if this makes sense.
</MenuItem> | ||
) | ||
)} | ||
</Select> |
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 don't think there's a great reason to use MUI for text fields or selects; we already have other components in the codebase for both cases, see e.g. https://docs.pyth.network/price-feeds/api-reference/cosmwasm/query-price-feed and the components on that page (Selector.tsx
for the select and Input.tsx
for the text input)
components/PriceFeedTable.tsx
Outdated
|
||
return ( | ||
<Box> | ||
<Box sx={{ mb: 3, display: "flex", gap: 2 }}> |
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.
The Box
component isn't useful with tailwind; these styles can be directly added to a div by using the correct utility classes, e.g. something like <div className="mb-1 flex gap-2">
components/PriceFeedTable.tsx
Outdated
autoHideDuration={2000} | ||
onClose={() => setOpenSnackbar(false)} | ||
message="Feed ID copied to clipboard" | ||
/> |
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 can avoid having to use this component by using the CopyToClipboard
nextra component which has feedback built in and is the same component that's used for code block copying
TODOs