Skip to content

Commit

Permalink
feat(forms): keep field props in memory during Wizard step change
Browse files Browse the repository at this point in the history
  • Loading branch information
tujoworker committed May 13, 2024
1 parent 9320841 commit dfe4e40
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 5 deletions.
Expand Up @@ -76,8 +76,7 @@ export interface ContextState {
checkFieldStateFor: (path: Path, state: SubmitState) => boolean
setFieldState: (path: Path, fieldState: SubmitState) => void
setFieldError: (path: Path, error: Error | FormError) => void
// Mounted fields - Components telling the provider what fields is on screen at any time
mountedFieldPaths: string[]
fieldPropsRef?: React.MutableRefObject<Record<string, FieldProps>>
handleMountField: (path: Path) => void
handleUnMountField: (path: Path) => void
formState: SubmitState
Expand Down Expand Up @@ -132,7 +131,6 @@ export const defaultContextState: ContextState = {
setFieldEventListener: () => null,
handleSubmitCall: () => null,
setShowAllErrors: () => null,
mountedFieldPaths: [],
handleMountField: () => null,
handleUnMountField: () => null,
hasErrors: () => false,
Expand Down
Expand Up @@ -980,7 +980,7 @@ export default function Provider<Data extends JsonObject>(
hasContext: true,
errors: errorsRef.current,
showAllErrors: showAllErrorsRef.current,
mountedFieldPaths: mountedFieldPathsRef.current,
fieldPropsRef,
ajvInstance: ajvRef.current,

/** Additional */
Expand Down
Expand Up @@ -24,7 +24,7 @@ import {
useSharedState,
} from '../../../../shared/helpers/useSharedState'
import useHandleLayoutEffect from './useHandleLayoutEffect'
import { ComponentProps } from '../../types'
import { ComponentProps, FieldProps } from '../../types'

// SSR warning fix: https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85
const useLayoutEffect =
Expand Down Expand Up @@ -71,13 +71,15 @@ function WizardContainer(props: Props) {
setShowAllErrors,
showAllErrors,
setSubmitState,
fieldPropsRef,
} = useContext(DataContext)

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

// - Handle shared state
const sharedStateRef =
Expand Down Expand Up @@ -128,6 +130,8 @@ function WizardContainer(props: Props) {
index: StepIndex
mode: 'previous' | 'next'
} & SetActiveIndexOptions) => {
fieldPropsMemoryRef.current = fieldPropsRef.current

handleSubmitCall({
skipErrorCheck,
skipFieldValidation: skipErrorCheck,
Expand All @@ -154,6 +158,9 @@ function WizardContainer(props: Props) {
if (!(result instanceof Error)) {
handleLayoutEffect()

// Revert the fieldProps to the previous state
fieldPropsRef.current = fieldPropsMemoryRef.current

activeIndexRef.current = index
forceUpdate()
}
Expand All @@ -164,6 +171,7 @@ function WizardContainer(props: Props) {
},
[
callOnStepChange,
fieldPropsRef,
handleLayoutEffect,
handleSubmitCall,
isInteractionRef,
Expand Down
Expand Up @@ -1088,4 +1088,107 @@ describe('Wizard.Container', () => {

log.mockRestore()
})

it('should keep field props in memory during step change', async () => {
const filterDataHandler = (path, value, props) => {
if (props['data-exclude-field']) {
return false
}
}

let currentData = null
let filteredData = null

const MyForm = () => {
const { data, filterData } = Form.useData('my-form')
currentData = data
filteredData = filterData(filterDataHandler)

return (
<Form.Handler id="my-form">
<Wizard.Container>
<Wizard.Step title="Step 1">
<output>Step 1</output>
<Field.String path="/fooStep1" />
<Field.String path="/barStep1" data-exclude-field />
<Wizard.Buttons />
</Wizard.Step>

<Wizard.Step title="Step 2">
<output>Step 2</output>
<Field.String path="/fooStep2" />
<Field.String path="/barStep2" data-exclude-field />
<Wizard.Buttons />
</Wizard.Step>

<Wizard.Step title="Step 3">
<output>Step 3</output>
<Field.String path="/fooStep3" />
<Field.String path="/barStep3" data-exclude-field />
<Wizard.Buttons />
</Wizard.Step>
</Wizard.Container>
</Form.Handler>
)
}

render(<MyForm />)

expect(currentData).toMatchObject({
barStep1: undefined,
fooStep1: undefined,
})

expect(filteredData).toMatchObject({
fooStep1: undefined,
})

await userEvent.click(nextButton())

expect(currentData).toMatchObject({
barStep1: undefined,
fooStep1: undefined,
fooStep2: undefined,
barStep2: undefined,
})

expect(filteredData).toMatchObject({
fooStep1: undefined,
fooStep2: undefined,
})

await userEvent.click(nextButton())

expect(currentData).toMatchObject({
barStep1: undefined,
fooStep1: undefined,
fooStep2: undefined,
barStep2: undefined,
fooStep3: undefined,
barStep3: undefined,
})

expect(filteredData).toMatchObject({
fooStep1: undefined,
fooStep2: undefined,
fooStep3: undefined,
})

await userEvent.click(previousButton())

expect(currentData).toMatchObject({
barStep1: undefined,
fooStep1: undefined,
fooStep2: undefined,
barStep2: undefined,
fooStep3: undefined,
barStep3: undefined,
})

expect(filteredData).toMatchObject({
fooStep1: undefined,
fooStep2: undefined,
fooStep3: undefined,
})
})
})

0 comments on commit dfe4e40

Please sign in to comment.