@@ -8,6 +8,7 @@ import { expect, userEvent, within, waitFor } from '@storybook/test';
88import { useState , useMemo , useCallback } from 'react' ;
99import { useFetcher } from 'react-router' ;
1010import { useRemixForm , RemixFormProvider , getValidatedFormData } from 'remix-hook-form' ;
11+ import { useFormContext } from 'react-hook-form' ;
1112import { z } from 'zod' ;
1213import type { ActionFunctionArgs } from 'react-router' ;
1314import { selectRadixOption } from '../lib/storybook/test-utils' ;
@@ -219,105 +220,107 @@ const orderSchema = z.object({
219220
220221type OrderFormData = z . infer < typeof orderSchema > ;
221222
222- const AutoCalculationExample = ( ) => {
223- const fetcher = useFetcher < { message : string } > ( ) ;
224-
225- const rawMethods = useRemixForm < OrderFormData > ( {
226- resolver : zodResolver ( orderSchema ) ,
227- defaultValues : {
228- quantity : '1' ,
229- pricePerUnit : '100' ,
230- discount : '0' ,
231- total : '100.00' ,
232- } ,
233- fetcher,
234- submitConfig : {
235- action : '/' ,
236- method : 'post' ,
237- } ,
238- } ) ;
239-
240- // Memoize methods to prevent unnecessary re-renders of the story tree
241- // which can disrupt interaction tests using Portals
242- const methods = useMemo ( ( ) => rawMethods , [ rawMethods ] ) ;
223+ // biome-ignore lint/suspicious/noExplicitAny: simple story example
224+ const AutoCalculationForm = ( { fetcher } : { fetcher : any } ) => {
225+ const { setValue, getValues, handleSubmit } = useFormContext < OrderFormData > ( ) ;
243226
244227 const calculateTotal = useCallback ( ( ) => {
245- const quantity = Number . parseFloat ( methods . getValues ( 'quantity' ) || '0' ) ;
246- const pricePerUnit = Number . parseFloat ( methods . getValues ( 'pricePerUnit' ) || '0' ) ;
247- const discount = Number . parseFloat ( methods . getValues ( 'discount' ) || '0' ) ;
228+ const quantity = Number . parseFloat ( getValues ( 'quantity' ) || '0' ) ;
229+ const pricePerUnit = Number . parseFloat ( getValues ( 'pricePerUnit' ) || '0' ) ;
230+ const discount = Number . parseFloat ( getValues ( 'discount' ) || '0' ) ;
248231
249232 const subtotal = quantity * pricePerUnit ;
250233 const total = subtotal - subtotal * ( discount / 100 ) ;
251- methods . setValue ( 'total' , total . toFixed ( 2 ) ) ;
252- } , [ methods ] ) ;
234+ setValue ( 'total' , total . toFixed ( 2 ) ) ;
235+ } , [ setValue , getValues ] ) ;
253236
254237 // Recalculate when quantity changes
255238 useOnFormValueChange ( {
256239 name : 'quantity' ,
257- methods,
258240 onChange : calculateTotal ,
259241 } ) ;
260242
261243 // Recalculate when price changes
262244 useOnFormValueChange ( {
263245 name : 'pricePerUnit' ,
264- methods,
265246 onChange : calculateTotal ,
266247 } ) ;
267248
268249 // Recalculate when discount changes
269250 useOnFormValueChange ( {
270251 name : 'discount' ,
271- methods,
272252 onChange : calculateTotal ,
273253 } ) ;
274254
255+ return (
256+ < form onSubmit = { handleSubmit } className = "w-96" >
257+ < div className = "space-y-6" >
258+ < TextField type = "number" name = "quantity" label = "Quantity" description = "Number of items" min = { 1 } />
259+
260+ < TextField
261+ type = "number"
262+ name = "pricePerUnit"
263+ label = "Price per Unit"
264+ description = "Price for each item"
265+ prefix = "$"
266+ min = { 0 }
267+ step = "0.01"
268+ />
269+
270+ < TextField
271+ type = "number"
272+ name = "discount"
273+ label = "Discount"
274+ description = "Discount percentage (0-100)"
275+ suffix = "%"
276+ min = { 0 }
277+ max = { 100 }
278+ />
279+
280+ < TextField
281+ name = "total"
282+ label = "Total"
283+ description = "Automatically calculated total"
284+ prefix = "$"
285+ disabled
286+ className = "font-semibold"
287+ />
288+
289+ < Button type = "submit" className = "w-full" >
290+ Submit Order
291+ </ Button >
292+ { fetcher . data ?. message && < p className = "mt-2 text-green-600" > { fetcher . data . message } </ p > }
293+ </ div >
294+ </ form >
295+ ) ;
296+ } ;
297+
298+ const AutoCalculationExample = ( ) => {
299+ const fetcher = useFetcher < { message : string } > ( ) ;
300+
301+ const methods = useRemixForm < OrderFormData > ( {
302+ resolver : zodResolver ( orderSchema ) ,
303+ defaultValues : {
304+ quantity : '1' ,
305+ pricePerUnit : '100' ,
306+ discount : '0' ,
307+ total : '100.00' ,
308+ } ,
309+ fetcher,
310+ submitConfig : {
311+ action : '/' ,
312+ method : 'post' ,
313+ } ,
314+ } ) ;
315+
275316 // Don't render if methods is not ready
276317 if ( ! methods || ! methods . handleSubmit ) {
277318 return < div > Loading...</ div > ;
278319 }
279320
280321 return (
281322 < RemixFormProvider { ...methods } >
282- < form onSubmit = { methods . handleSubmit } className = "w-96" >
283- < div className = "space-y-6" >
284- < TextField type = "number" name = "quantity" label = "Quantity" description = "Number of items" min = { 1 } />
285-
286- < TextField
287- type = "number"
288- name = "pricePerUnit"
289- label = "Price per Unit"
290- description = "Price for each item"
291- prefix = "$"
292- min = { 0 }
293- step = "0.01"
294- />
295-
296- < TextField
297- type = "number"
298- name = "discount"
299- label = "Discount"
300- description = "Discount percentage (0-100)"
301- suffix = "%"
302- min = { 0 }
303- max = { 100 }
304- />
305-
306- < TextField
307- name = "total"
308- label = "Total"
309- description = "Automatically calculated total"
310- prefix = "$"
311- disabled
312- className = "font-semibold"
313- />
314-
315- < Button type = "submit" className = "w-full" >
316- Submit Order
317- </ Button >
318- { fetcher . data ?. message && < p className = "mt-2 text-green-600" > { fetcher . data . message } </ p > }
319- </ div >
320- </ form >
323+ < AutoCalculationForm fetcher = { fetcher } />
321324 </ RemixFormProvider >
322325 ) ;
323326} ;
0 commit comments