Add form validation for add network form (#101)

* Use zod for validation

* Return to index 0 after deleting network

* Make url optional

* Use form schema according to the selected nw

* Fix type for networks data

* Accept numeric value for coin type

* Fix form type issue
This commit is contained in:
IshaVenikar 2024-04-17 18:39:12 +05:30 committed by Nabarun Gogoi
parent b947dd1151
commit 670d6f4a54
5 changed files with 71 additions and 21 deletions

View File

@ -16,6 +16,7 @@
"@cosmjs/proto-signing": "^0.32.3", "@cosmjs/proto-signing": "^0.32.3",
"@cosmjs/stargate": "^0.32.3", "@cosmjs/stargate": "^0.32.3",
"@ethersproject/shims": "^5.7.0", "@ethersproject/shims": "^5.7.0",
"@hookform/resolvers": "^3.3.4",
"@json-rpc-tools/utils": "^1.7.6", "@json-rpc-tools/utils": "^1.7.6",
"@react-native-async-storage/async-storage": "^1.22.3", "@react-native-async-storage/async-storage": "^1.22.3",
"@react-native-community/netinfo": "^11.3.1", "@react-native-community/netinfo": "^11.3.1",
@ -46,7 +47,8 @@
"react-native-vector-icons": "^10.0.3", "react-native-vector-icons": "^10.0.3",
"react-native-vision-camera": "^3.9.0", "react-native-vision-camera": "^3.9.0",
"text-encoding-polyfill": "^0.6.7", "text-encoding-polyfill": "^0.6.7",
"use-debounce": "^10.0.0" "use-debounce": "^10.0.0",
"zod": "^3.22.4"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.0", "@babel/core": "^7.20.0",

View File

@ -21,7 +21,7 @@ const Accounts = ({ currentIndex, updateIndex }: AccountsProps) => {
const navigation = const navigation =
useNavigation<NativeStackNavigationProp<StackParamsList>>(); useNavigation<NativeStackNavigationProp<StackParamsList>>();
const { accounts, setAccounts } = useAccounts(); const { accounts, setAccounts, setCurrentIndex } = useAccounts();
const { networksData, selectedNetwork, setNetworksData, setSelectedNetwork } = const { networksData, selectedNetwork, setNetworksData, setSelectedNetwork } =
useNetworks(); useNetworks();
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
@ -108,6 +108,7 @@ const Accounts = ({ currentIndex, updateIndex }: AccountsProps) => {
); );
setSelectedNetwork(updatedNetworks[0]); setSelectedNetwork(updatedNetworks[0]);
setCurrentIndex(0);
setDeleteNetworkDialog(false); setDeleteNetworkDialog(false);
setNetworksData(updatedNetworks); setNetworksData(updatedNetworks);
}; };

View File

@ -25,7 +25,7 @@ const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => {
onPress={() => setExpanded(!expanded)}> onPress={() => setExpanded(!expanded)}>
{networksData.map(networkData => ( {networksData.map(networkData => (
<List.Item <List.Item
key={networkData.chainId} key={networkData.networkId}
title={networkData.networkName} title={networkData.networkName}
onPress={() => handleNetworkPress(networkData)} onPress={() => handleNetworkPress(networkData)}
/> />

View File

@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { ScrollView } from 'react-native'; import { ScrollView } from 'react-native';
import { useForm, Controller, useWatch } from 'react-hook-form'; import { useForm, Controller, useWatch, FieldErrors } from 'react-hook-form';
import { TextInput, Button, HelperText } from 'react-native-paper'; import { TextInput, Button, HelperText } from 'react-native-paper';
import { import {
getInternetCredentials, getInternetCredentials,
@ -9,12 +9,14 @@ import {
import { HDNode } from 'ethers/lib/utils'; import { HDNode } from 'ethers/lib/utils';
import { chains } from 'chain-registry'; import { chains } from 'chain-registry';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
import { z } from 'zod';
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { zodResolver } from '@hookform/resolvers/zod';
import styles from '../styles/stylesheet'; import styles from '../styles/stylesheet';
import { NetworksDataState, NetworksFormData, StackParamsList } from '../types'; import { StackParamsList } from '../types';
import { SelectNetworkType } from '../components/SelectNetworkType'; import { SelectNetworkType } from '../components/SelectNetworkType';
import { storeNetworkData } from '../utils/accounts'; import { storeNetworkData } from '../utils/accounts';
import { useNetworks } from '../context/NetworksContext'; import { useNetworks } from '../context/NetworksContext';
@ -22,19 +24,45 @@ import { COSMOS, EIP155, CHAINID_DEBOUNCE_DELAY } from '../utils/constants';
import { getCosmosAccounts } from '../utils/accounts'; import { getCosmosAccounts } from '../utils/accounts';
import ETH_CHAINS from '../assets/ethereum-chains.json'; import ETH_CHAINS from '../assets/ethereum-chains.json';
// TODO: Add validation to form inputs const ethNetworkDataSchema = z.object({
chainId: z.string().min(1),
networkName: z.string().min(1),
rpcUrl: z.string().url(),
blockExplorerUrl: z.string().url().or(z.literal('')),
coinType: z.string().regex(/^\d+$/).min(1),
currencySymbol: z.string().min(1),
});
const cosmosNetworkDataSchema = z.object({
chainId: z.string().min(1),
networkName: z.string().min(1),
rpcUrl: z.string().url(),
blockExplorerUrl: z.string().url().or(z.literal('')),
coinType: z.string().regex(/^\d+$/).min(1),
nativeDenom: z.string().min(1),
addressPrefix: z.string().min(1),
});
const AddNetwork = () => { const AddNetwork = () => {
const navigation = const navigation =
useNavigation<NativeStackNavigationProp<StackParamsList>>(); useNavigation<NativeStackNavigationProp<StackParamsList>>();
const { setNetworksData } = useNetworks();
const [namespace, setNamespace] = useState<string>(EIP155);
const networksFormDataSchema =
namespace === EIP155 ? ethNetworkDataSchema : cosmosNetworkDataSchema;
const { const {
control, control,
formState: { errors, isValid }, formState: { errors },
handleSubmit, handleSubmit,
setValue, setValue,
reset, reset,
} = useForm<NetworksDataState>({ } = useForm<z.infer<typeof networksFormDataSchema>>({
mode: 'onChange', mode: 'onChange',
resolver: zodResolver(networksFormDataSchema),
}); });
const watchChainId = useWatch({ const watchChainId = useWatch({
@ -42,10 +70,6 @@ const AddNetwork = () => {
name: 'chainId', name: 'chainId',
}); });
const { setNetworksData } = useNetworks();
const [namespace, setNamespace] = useState<string>(EIP155);
const updateNetworkType = (newNetworkType: string) => { const updateNetworkType = (newNetworkType: string) => {
setNamespace(newNetworkType); setNamespace(newNetworkType);
}; };
@ -80,7 +104,7 @@ const AddNetwork = () => {
}, CHAINID_DEBOUNCE_DELAY); }, CHAINID_DEBOUNCE_DELAY);
const submit = useCallback( const submit = useCallback(
async (data: NetworksFormData) => { async (data: z.infer<typeof networksFormDataSchema>) => {
const newNetworkData = { const newNetworkData = {
...data, ...data,
namespace, namespace,
@ -110,7 +134,8 @@ const AddNetwork = () => {
await getCosmosAccounts( await getCosmosAccounts(
mnemonic, mnemonic,
hdPath, hdPath,
newNetworkData.addressPrefix, (newNetworkData as z.infer<typeof cosmosNetworkDataSchema>)
.addressPrefix,
) )
).data.address; ).data.address;
break; break;
@ -262,7 +287,10 @@ const AddNetwork = () => {
onChangeText={textValue => onChange(textValue)} onChangeText={textValue => onChange(textValue)}
/> />
<HelperText type="error"> <HelperText type="error">
{errors.currencySymbol?.message} {
(errors as FieldErrors<z.infer<typeof ethNetworkDataSchema>>)
.currencySymbol?.message
}
</HelperText> </HelperText>
</> </>
)} )}
@ -283,7 +311,13 @@ const AddNetwork = () => {
onChangeText={textValue => onChange(textValue)} onChangeText={textValue => onChange(textValue)}
/> />
<HelperText type="error"> <HelperText type="error">
{errors.nativeDenom?.message} {
(
errors as FieldErrors<
z.infer<typeof cosmosNetworkDataSchema>
>
).nativeDenom?.message
}
</HelperText> </HelperText>
</> </>
)} )}
@ -302,17 +336,20 @@ const AddNetwork = () => {
onChangeText={textValue => onChange(textValue)} onChangeText={textValue => onChange(textValue)}
/> />
<HelperText type="error"> <HelperText type="error">
{errors.addressPrefix?.message} {
(
errors as FieldErrors<
z.infer<typeof cosmosNetworkDataSchema>
>
).addressPrefix?.message
}
</HelperText> </HelperText>
</> </>
)} )}
/> />
</> </>
)} )}
<Button <Button mode="contained" onPress={handleSubmit(submit)}>
mode="contained"
onPress={handleSubmit(submit)}
disabled={!isValid}>
Submit Submit
</Button> </Button>
</ScrollView> </ScrollView>

View File

@ -1692,6 +1692,11 @@
dependencies: dependencies:
"@hapi/hoek" "^9.0.0" "@hapi/hoek" "^9.0.0"
"@hookform/resolvers@^3.3.4":
version "3.3.4"
resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.3.4.tgz#de9b668c2835eb06892290192de6e2a5c906229b"
integrity sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==
"@humanwhocodes/config-array@^0.11.13": "@humanwhocodes/config-array@^0.11.13":
version "0.11.14" version "0.11.14"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"
@ -9361,3 +9366,8 @@ yocto-queue@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zod@^3.22.4:
version "3.22.4"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"
integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==