Skip to content

Commit

Permalink
fix(forms): ensure pressing enter in an input field inside Wizard doe…
Browse files Browse the repository at this point in the history
…s trigger next step (#3852)
  • Loading branch information
tujoworker authored Aug 21, 2024
1 parent 2a2272e commit fd35828
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 18 deletions.
12 changes: 9 additions & 3 deletions packages/dnb-eufemia/src/extensions/forms/DataContext/Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type HandleSubmitProps = {
export type EventListenerCall = {
path?: Path
type?: 'onSubmit'
callback: () => any
callback: () => void
}

export type FilterDataHandler<Data> = (
Expand Down Expand Up @@ -54,6 +54,11 @@ export type FilterData<Data = unknown> =
| FilterDataPathObject<Data>
| FilterDataHandlerCallback<boolean | undefined>
export type TransformData = FilterDataHandlerCallback<unknown>
export type HandleSubmitCallback = ({
preventSubmit,
}: {
preventSubmit: () => void
}) => void

export interface ContextState {
id?: Identifier
Expand Down Expand Up @@ -110,8 +115,9 @@ export interface ContextState {
callback: EventListenerCall['callback']
) => void
setHasVisibleError?: (path: Path, hasError: boolean) => void
setFieldProps?: (path: Path, props: any) => void
setValueProps?: (path: Path, props: any) => void
setFieldProps?: (path: Path, props: unknown) => void
setValueProps?: (path: Path, props: unknown) => void
setHandleSubmit?: (callback: HandleSubmitCallback) => void
fieldPropsRef?: React.MutableRefObject<Record<string, FieldProps>>
valuePropsRef?: React.MutableRefObject<Record<string, ValueProps>>
mountedFieldPathsRef?: React.MutableRefObject<Path[]>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import Context, {
ContextState,
EventListenerCall,
FilterData,
HandleSubmitCallback,
TransformData,
} from '../Context'

Expand Down Expand Up @@ -960,6 +961,24 @@ export default function Provider<Data extends JsonObject>(
]
)

const handleSubmitListenersRef = useRef<Array<HandleSubmitCallback>>([])
const setHandleSubmit: ContextState['setHandleSubmit'] = useCallback(
(callback) => {
if (!handleSubmitListenersRef.current.includes(callback)) {
handleSubmitListenersRef.current.push(callback)
}
},
[]
)
const handleSubmitListeners = useCallback(() => {
let stop = false
const preventSubmit = () => (stop = true)
handleSubmitListenersRef.current.forEach((cb) => {
cb({ preventSubmit })
})
return stop
}, [])

/**
* Request to submit the whole form
*/
Expand All @@ -968,6 +987,10 @@ export default function Provider<Data extends JsonObject>(
handleSubmitCall({
enableAsyncBehaviour: isAsync(onSubmit),
onSubmit: async () => {
if (handleSubmitListeners()) {
return // stop here
}

// - Mutate the data context
const data = internalDataRef.current
const filteredData = filterDataHandler(
Expand Down Expand Up @@ -1124,6 +1147,7 @@ export default function Provider<Data extends JsonObject>(
setData,
filterDataHandler,
addOnChangeHandler,
setHandleSubmit,
scrollToTop,

/** State handling */
Expand Down
29 changes: 16 additions & 13 deletions packages/dnb-eufemia/src/extensions/forms/Form/Element/Element.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useCallback, useContext } from 'react'
import Context from '../../DataContext/Context'
import Space from '../../../../components/space/Space'
import classnames from 'classnames'
Expand All @@ -12,14 +12,28 @@ export default function FormElement({
onSubmit = null,
...rest
}: Props) {
const dataContext = React.useContext(Context)
const dataContext = useContext(Context)

/**
* Set to true,
* this way we prevent "handleSubmit" to be called twice when the SubmitButton is pressed.
*/
dataContext.isInsideFormElement = true

const onSubmitHandler = useCallback(
(event: React.SyntheticEvent<HTMLFormElement>) => {
event?.preventDefault()

const formElement = event.target as HTMLFormElement
dataContext?.handleSubmit?.({ formElement })

if (typeof onSubmit === 'function') {
onSubmit(event)
}
},
[dataContext, onSubmit]
)

return (
<Space
element="form"
Expand All @@ -30,15 +44,4 @@ export default function FormElement({
{children}
</Space>
)

function onSubmitHandler(event: React.SyntheticEvent<HTMLFormElement>) {
event?.preventDefault()

const formElement = event.target as HTMLFormElement
dataContext?.handleSubmit?.({ formElement })

if (typeof onSubmit === 'function') {
onSubmit(event)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,18 +110,20 @@ function WizardContainer(props: Props) {
...rest
} = props

const dataContext = useContext(DataContext)
const {
hasContext,
setFormState,
handleSubmitCall,
setShowAllErrors,
showAllErrors,
setSubmitState,
} = useContext(DataContext)
} = dataContext

const id = useId(idProp)
const [, forceUpdate] = useReducer(() => ({}), {})
const activeIndexRef = useRef<StepIndex>(initialActiveIndex)
const totalStepsRef = useRef<number>(NaN)
const errorOnStepRef = useRef<Record<StepIndex, boolean>>({})
const stepElementRef = useRef<HTMLElement>()

Expand Down Expand Up @@ -259,6 +261,17 @@ function WizardContainer(props: Props) {
[setSubmitState]
)

const handleSubmit = useCallback(
({ preventSubmit }) => {
if (activeIndexRef.current + 1 < totalStepsRef.current) {
handleNext()
preventSubmit()
}
},
[handleNext]
)
dataContext.setHandleSubmit?.(handleSubmit)

const titlesRef = useRef({})
const updateTitlesRef = useRef<() => void>()
const prerenderFieldPropsRef = useRef<
Expand All @@ -268,14 +281,15 @@ function WizardContainer(props: Props) {
const { check } = useVisibility()

const activeIndex = activeIndexRef.current
const providerValue = useMemo(() => {
const providerValue = useMemo<WizardContextState>(() => {
return {
id,
activeIndex,
stepElementRef,
titlesRef,
updateTitlesRef,
activeIndexRef,
totalStepsRef,
prerenderFieldProps,
prerenderFieldPropsRef,
check,
Expand Down Expand Up @@ -386,6 +400,7 @@ function IterateOverSteps({ children }) {
check,
titlesRef,
activeIndexRef,
totalStepsRef,
prerenderFieldProps,
prerenderFieldPropsRef,
} = useContext(WizardContext)
Expand Down Expand Up @@ -463,6 +478,8 @@ function IterateOverSteps({ children }) {
activeIndexRef.current = childrenArray.length - 1
}

totalStepsRef.current = childrenArray?.length

return childrenArray
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,59 @@ describe('Wizard.Container', () => {
})
})

it('should trigger next step when submitting the form', async () => {
const onSubmit = jest.fn()
const onStepChange = jest.fn()

render(
<Form.Handler onSubmit={onSubmit}>
<Wizard.Container onStepChange={onStepChange} mode="loose">
<Wizard.Step title="Step 1">
<output>Step 1</output>
<Wizard.NextButton />
</Wizard.Step>

<Wizard.Step title="Step 2">
<output>Step 2</output>
<Wizard.PreviousButton />
<Wizard.NextButton />
</Wizard.Step>

<Wizard.Step title="Step 3">
<output>Step 3</output>
<Form.SubmitButton />
</Wizard.Step>
</Wizard.Container>
</Form.Handler>
)

expect(output()).toHaveTextContent('Step 1')

const form = document.querySelector('form')

fireEvent.submit(form)

expect(onSubmit).toHaveBeenCalledTimes(0)
expect(onStepChange).toHaveBeenCalledTimes(1)
await waitFor(() => {
expect(output()).toHaveTextContent('Step 2')
})

fireEvent.submit(form)

expect(onSubmit).toHaveBeenCalledTimes(0)
expect(onStepChange).toHaveBeenCalledTimes(2)
await waitFor(() => {
expect(output()).toHaveTextContent('Step 3')
})

fireEvent.submit(form)

expect(onSubmit).toHaveBeenCalledTimes(1)
expect(onStepChange).toHaveBeenCalledTimes(2)
expect(output()).toHaveTextContent('Step 3')
})

describe('dynamic steps', () => {
it('should not render inactive steps', () => {
render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface WizardContextState {
titlesRef?: React.MutableRefObject<Record<string, string>>
updateTitlesRef?: React.MutableRefObject<() => void>
activeIndexRef?: React.MutableRefObject<StepIndex>
totalStepsRef?: React.MutableRefObject<number>
prerenderFieldPropsRef?: React.MutableRefObject<
Record<string, () => React.ReactElement>
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ describe('useStep', () => {
<Wizard.Step>
<output>{JSON.stringify({ activeIndex })}</output>
<button
type="button"
className="dnb-forms-next-button"
onClick={() => {
setActiveIndex(activeIndex + 1)
Expand Down Expand Up @@ -107,6 +108,7 @@ describe('useStep', () => {
<Wizard.Step>
<output>{JSON.stringify({ activeIndex })}</output>
<button
type="button"
className="dnb-forms-next-button"
onClick={() => {
setActiveIndex(activeIndex + 1)
Expand Down Expand Up @@ -232,6 +234,7 @@ describe('useStep', () => {
const { activeIndex, setActiveIndex } = useStep(identifier)
return (
<button
type="button"
className="before"
onClick={() => {
setActiveIndex(activeIndex + 1)
Expand All @@ -246,6 +249,7 @@ describe('useStep', () => {
const { activeIndex, setActiveIndex } = useStep(identifier)
return (
<button
type="button"
className="after"
onClick={() => {
setActiveIndex(activeIndex + 1)
Expand Down Expand Up @@ -308,6 +312,7 @@ describe('useStep', () => {
<>
<output>{JSON.stringify({ activeIndex })}</output>
<button
type="button"
className="dnb-forms-next-button"
onClick={() => {
setActiveIndex(activeIndex + 1)
Expand Down

0 comments on commit fd35828

Please sign in to comment.