-
Notifications
You must be signed in to change notification settings - Fork 2
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
Dynamic client side envs 2 #80
Conversation
src/pages/_app.tsx
Outdated
<Analytics /> | ||
<SpeedInsights /> | ||
<Component {...pageProps} /> | ||
<EnvContext.Provider value={pageProps.envs}> |
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.
Meant as a placeholder until we have finalized a state manager.
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 we anticipate this env context vars to be critical to a number of things in client side code, we should use react-query to fetch and sync its state with a mobx EnvContextStore
.
Deploying from this tag, without updating the env vars, I see:
Which indicates that we no longer have sane default values. Can we add a default value for After setting the env vars properly, according to the new env vars, I confirmed that the assets were loading correctly for the testnet deployment, which is magnificent. |
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.
Works with the new env vars set! Asking for a bit more error-handling around the new vars: let's provide defaults, or at least log which required env vars weren't set if they're missing. See example error message in comments above.
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 is a lot in this pull request that opens a number of questions on how we want to handle data fetching within this app:
- client vs server requests
- async vs sync requests
- global caching & performance
- react-query syncing w/ state manager
- using a state manager versus react-context
Think it is worthwhile for others on the web team to take a look and we discuss in today's meeting
src/components/pairSelector.tsx
Outdated
const tokenAssetsList = useTokenAssets(); | ||
const [tokenAssets, setTokenAssets] = useState<Record<string, Token>>({}); | ||
const [selectedAssets, setSelectedAssets] = useState<Token[]>([]); | ||
|
||
useEffect(() => { | ||
const tokenAssets = fetchAllTokenAssets(); | ||
setTokenAssets(Object.fromEntries(tokenAssets.map(asset => [asset.symbol, asset]))); | ||
}, []); | ||
setTokenAssets(Object.fromEntries(tokenAssetsList.map(asset => [asset.symbol, asset]))); | ||
}, [tokenAssetsList]); |
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 be in react-query
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.
Correct, but let's keep that to another PR (also this is v1 code).
src/pages/block/[block_height].tsx
Outdated
export async function getServerSideProps() { | ||
const envs = getClientSideEnvs(); | ||
|
||
return { | ||
props: { | ||
envs, | ||
}, | ||
}; | ||
} |
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.
Two thoughts:
- Can we put this at a kind of parent layout component so we don't have to do this for every page we create (and need to remember or else risk errors)?
- What if we created an backend API route for returning this and we put it in react-query (client side) instead?
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.
- Unfortunately with this implementation it seems to be not possible
- Perhaps that's a better approach
src/utils/token/tokenFetch.tsx
Outdated
export function useTokenAssets() { | ||
const envs = useEnvContext(); | ||
return fetchAllTokenAssets(envs['PENUMBRA_CHAIN_ID'] ?? ''); | ||
} |
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.
As this is, I'm pretty sure that fetchAllTokenAssets() does not cache. If env context was in react-query, we could compose within react-query to handle caching globally for this work. Otherwise, every component's use of useTokenAssets() will be a new instance and re-running on every render.
src/pages/_app.tsx
Outdated
<Analytics /> | ||
<SpeedInsights /> | ||
<Component {...pageProps} /> | ||
<EnvContext.Provider value={pageProps.envs}> |
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 we anticipate this env context vars to be critical to a number of things in client side code, we should use react-query to fetch and sync its state with a mobx EnvContextStore
.
Changing the implementation to use a request to fetch the client-side env. WIP. |
src/fetchers/env.ts
Outdated
return useQuery({ | ||
queryKey: ['clientEnv'], | ||
queryFn: async (): Promise<ClientEnv> => { | ||
return fetch('/api/env').then(resp => resp.json() as unknown as ClientEnv); |
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.
suggestion: In async functions, I find sticking to async await to be more readable than promise chaining:
queryFn: async (): Promise<ClientEnv> => {
const res = await fetch('/api/env');
return (await res.json()) as ClientEnv;
},
src/fetchers/registry.ts
Outdated
queryKey: ['penumbraRegistry', chainId], | ||
queryFn: async (): Promise<Registry | null> => { | ||
if (!chainId) { | ||
return null; |
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.
issue: in general, let's try to avoid null when we can. This can go into bike shedding territory, but I'm in the camp that it is a mistake in the language.
There is another issue though, the returned loading and error states from useRegistry
are not quite true. We should combine them together:
export const useRegistry = () => {
const { data: env, isLoading: isEnvLoading, error: envError } = useEnv();
const {
data: registry,
isLoading: isRegistryLoading,
error: registryError,
} = useQuery({
queryKey: ['penumbraRegistry', env],
queryFn: async () => {
const chainId = env?.PENUMBRA_CHAIN_ID;
if (!chainId) {
throw new Error('chain id not available to query registry');
}
return chainRegistryClient.remote.get(chainId);
},
staleTime: Infinity,
enabled: Boolean(env),
});
return {
registry,
isLoading: isEnvLoading || isRegistryLoading,
error: envError || registryError,
};
};
src/fetchers/tokenAssets.ts
Outdated
import { uint8ArrayToBase64, base64ToUint8Array } from '@/old/utils/math/base64'; | ||
import { Token } from '@/old/utils/types/token'; | ||
|
||
export const useTokenAssets = (): Token[] => { |
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.
issue: with useRegistry()
returning data/loading/error, this hook should also return the same so that can properly be handled by consumers if desired.
src/fetchers/tokenAssets.ts
Outdated
}) as Token[]; | ||
}; | ||
|
||
export const useTokenAsset = (tokenId: Uint8Array | string): null | Token => { |
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.
issue: we should return some data field + loading + error states
inner: asset.penumbraAssetId?.inner && uint8ArrayToBase64(asset.penumbraAssetId.inner), | ||
imagePath: imagePathFromAssetImages(asset.images), | ||
}; | ||
}) as Token[]; |
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.
issue: it is tempting to use our own types, but we should do our best to use types from BufBuild that are more universally relied upon in the penumbra ecosystem. For instance, we shouldn't have components that accept a Token, but probably ones that accept a ValueView
type
src/utils/env/getClientSideEnv.ts
Outdated
export function getClientSideEnv() { | ||
const whitelist: string[] = ['PENUMBRA_CHAIN_ID', 'PENUMBRA_CUILOA_URL']; | ||
|
||
const env = whitelist.reduce( | ||
(env, key) => ({ | ||
...env, | ||
[key]: process.env[key] ?? '', | ||
}), | ||
defaults, | ||
) as ClientEnv; | ||
|
||
return env; | ||
} |
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.
suggestion: Think this would work the same
export function getClientSideEnv(): ClientEnv {
const whitelist: string[] = ['PENUMBRA_CHAIN_ID', 'PENUMBRA_CUILOA_URL'];
return whitelist.reduce(
(env, key) => ({
...env,
[key]: process.env[key] ?? '',
}),
defaults,
);
}
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.
Talked offline:
- For hooks consumed by deprecated code, label as deprecated. Given they don't consume loading/error states, can keep simple as needed.
- For hooks consumed by new code, should follow data/loading/error propagation pattern
- No nulls (if possible)
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.
Beautiful! Thanks for your hard work on this, it's markedly more straightforward to deploy and configure now.
It's a nice touch to redirect the trading view to penumbra:gm
on the testnet; however, that route should probably be configurable, too, maybe with the previous setting of penumbra:usdc
retained as default behavior. Otherwise, deployments against the penumbra-1
chain will show nonsense data, because there's no gm
asset. But we can circle back to that change later, I know there' a lot of work in-flight right now!
As per @conorsch 's request, this adds support to set envs dynamically post-build.
Routes need to pass envs from getServerSideProps as props_app will take this and provide it as a context (in v2 we could use mobx or whatever state manager we decide on)Components can consume useEnvContext to access the envs