Reinitialize wallet form if initial values change + fix readOnly hydration error (#1354)

* Reinitialize wallet form if initial values change

This fixes that enabled is not set on first render if only recv is configured

* Remove unnecessary old usage of ClientCheckbox

This isn't needed even without enableReinitialize since for send, enabled is correctly set on first render.

It was needed in the past when we were still validating wallets before enabling them on first page load but now, we simply load the configuration from localStorage which is immediately available on the client.

* Fix readOnly hydration error

* Replace repetitive isMounted logic with useIsClient hook
This commit is contained in:
ekzyis 2024-09-03 16:15:04 +02:00 committed by GitHub
parent 69916117b1
commit d9024ff837
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 29 additions and 21 deletions

View File

@ -3,6 +3,7 @@ import { Checkbox, Input } from './form'
import { useMe } from './me'
import { useEffect, useState } from 'react'
import { isNumber } from '@/lib/validate'
import { useIsClient } from './use-client'
function autoWithdrawThreshold ({ me }) {
return isNumber(me?.privates?.autoWithdrawThreshold) ? me?.privates?.autoWithdrawThreshold : 10000
@ -25,15 +26,12 @@ export function AutowithdrawSettings ({ wallet }) {
setSendThreshold(Math.max(Math.floor(threshold / 10), 1))
}, [autoWithdrawThreshold])
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
}, [])
const isClient = useIsClient()
return (
<>
<Checkbox
disabled={mounted && !wallet.isConfigured}
disabled={isClient && !wallet.isConfigured}
label='enabled'
id='enabled'
name='enabled'

View File

@ -803,7 +803,7 @@ const StorageKeyPrefixContext = createContext()
export function Form ({
initial, validate, schema, onSubmit, children, initialError, validateImmediately,
storageKeyPrefix, validateOnChange = true, requireSession, innerRef,
storageKeyPrefix, validateOnChange = true, requireSession, innerRef, enableReinitialize,
...props
}) {
const toaster = useToast()
@ -855,6 +855,7 @@ export function Form ({
return (
<Formik
initialValues={initial}
enableReinitialize={enableReinitialize}
validateOnChange={validateOnChange}
validate={validate}
validationSchema={schema}

12
components/use-client.js Normal file
View File

@ -0,0 +1,12 @@
import { useEffect, useState } from 'react'
// https://usehooks-ts.com/react-hook/use-is-client#hook
export function useIsClient () {
const [isClient, setClient] = useState(false)
useEffect(() => {
setClient(true)
}, [])
return isClient
}

View File

@ -1,15 +1,16 @@
import { getGetServerSideProps } from '@/api/ssrApollo'
import { Form, ClientInput, ClientCheckbox, PasswordInput, CheckboxGroup } from '@/components/form'
import { Form, ClientInput, PasswordInput, CheckboxGroup, Checkbox } from '@/components/form'
import { CenterLayout } from '@/components/layout'
import { WalletSecurityBanner } from '@/components/banners'
import { WalletLogs } from '@/components/wallet-logger'
import { useToast } from '@/components/toast'
import { useRouter } from 'next/router'
import { useWallet, Status } from 'wallets'
import { useWallet } from 'wallets'
import Info from '@/components/info'
import Text from '@/components/text'
import { AutowithdrawSettings } from '@/components/autowithdraw-shared'
import dynamic from 'next/dynamic'
import { useIsClient } from '@/components/use-client'
const WalletButtonBar = dynamic(() => import('@/components/wallet-buttonbar.js'), { ssr: false })
@ -45,6 +46,7 @@ export default function WalletSettings () {
{wallet.canSend && wallet.hasConfig > 0 && <WalletSecurityBanner />}
<Form
initial={initial}
enableReinitialize
{...validateProps}
onSubmit={async ({ amount, ...values }) => {
try {
@ -70,9 +72,8 @@ export default function WalletSettings () {
? <AutowithdrawSettings wallet={wallet} />
: (
<CheckboxGroup name='enabled'>
<ClientCheckbox
<Checkbox
disabled={!wallet.isConfigured}
initialValue={wallet.status === Status.Enabled}
label='enabled'
name='enabled'
groupClassName='mb-0'
@ -101,13 +102,15 @@ export default function WalletSettings () {
}
function WalletFields ({ wallet: { config, fields, isConfigured } }) {
const isClient = useIsClient()
return fields
.map(({ name, label = '', type, help, optional, editable, clientOnly, serverOnly, ...props }, i) => {
const rawProps = {
...props,
name,
initialValue: config?.[name],
readOnly: isConfigured && editable === false && !!config?.[name],
readOnly: isClient && isConfigured && editable === false && !!config?.[name],
groupClassName: props.hidden ? 'd-none' : undefined,
label: label
? (

View File

@ -3,8 +3,9 @@ import Layout from '@/components/layout'
import styles from '@/styles/wallet.module.css'
import Link from 'next/link'
import { useWallets, walletPrioritySort } from 'wallets'
import { useEffect, useState } from 'react'
import { useState } from 'react'
import dynamic from 'next/dynamic'
import { useIsClient } from '@/components/use-client'
const WalletCard = dynamic(() => import('@/components/wallet-card'), { ssr: false })
@ -29,17 +30,10 @@ async function reorder (wallets, sourceIndex, targetIndex) {
export default function Wallet ({ ssrData }) {
const { wallets } = useWallets()
const [mounted, setMounted] = useState(false)
const isClient = useIsClient()
const [sourceIndex, setSourceIndex] = useState(null)
const [targetIndex, setTargetIndex] = useState(null)
useEffect(() => {
// mounted is required since draggable is false
// for wallets only available on the client during SSR
// and thus we need to render the component again on the client
setMounted(true)
}, [])
const onDragStart = (i) => (e) => {
// e.dataTransfer.dropEffect = 'move'
// We can only use the DataTransfer API inside the drop event
@ -94,7 +88,7 @@ export default function Wallet ({ ssrData }) {
return walletPrioritySort(w1, w2)
})
.map((w, i) => {
const draggable = mounted && w.enabled
const draggable = isClient && w.enabled
return (
<div