Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ export type FormComponentProps = Partial<
resetOnSuccess?: boolean | string[]
resetOnError?: boolean | string[]
setDefaultsOnSuccess?: boolean
defaultValues?: Record<string, FormDataConvertible>
}

export type FormComponentMethods = {
Expand Down
26 changes: 22 additions & 4 deletions packages/react/src/Form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,13 @@ const Form = forwardRef<FormComponentRef, ComponentProps>(
resetOnSuccess = false,
setDefaultsOnSuccess = false,
invalidateCacheTags = [],
defaultValues = {},
children,
...props
},
ref,
) => {
const form = useForm<Record<string, any>>({})
const form = useForm<Record<string, any>>(defaultValues)
const formElement = useRef<HTMLFormElement>(null)

const resolvedMethod = useMemo(() => {
Expand All @@ -80,15 +81,32 @@ const Form = forwardRef<FormComponentRef, ComponentProps>(
// expects an object, and submitting a FormData instance directly causes problems with nested objects.
const getData = (): Record<string, FormDataConvertible> => formDataToObject(getFormData())

const initializeDefaultData = () => {
if (formElement.current) {
defaultData.current = getFormData()
Object.keys(defaultValues).forEach((key) => applyDefaultValue(key, defaultValues[key]))
// Reset the form to ensure default values are applied to the actual form fields
reset()
}
}

const applyDefaultValue = (key: string, value: any) => {
if (typeof value === 'boolean') {
defaultData.current.set(key, value ? 'on' : '')
return
}

defaultData.current.set(key, value)
}

const updateDirtyState = (event: Event) =>
setIsDirty(event.type === 'reset' ? false : !isEqual(getData(), formDataToObject(defaultData.current)))

useEffect(() => {
defaultData.current = getFormData()

initializeDefaultData()
const formEvents: Array<keyof HTMLElementEventMap> = ['input', 'change', 'reset']

formEvents.forEach((e) => formElement.current.addEventListener(e, updateDirtyState))
formEvents.forEach((e) => formElement.current?.addEventListener(e, updateDirtyState))

return () => formEvents.forEach((e) => formElement.current?.removeEventListener(e, updateDirtyState))
}, [])
Expand Down
76 changes: 76 additions & 0 deletions packages/react/test-app/Pages/FormComponent/DefaultValues.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Form } from '@inertiajs/react'

export default function DefaultValues() {
return (
<div>
<h1>Form with Default Values</h1>

<Form
action="/dump/post"
method="post"
defaultValues={{
name: 'John Doe',
email: '[email protected]',
role: 'admin',
newsletter: true,
preferences: 'option2',
}}
>
{({ isDirty, hasErrors, errors }) => (
<>
{/* State display for testing */}
<div>Form is {isDirty ? 'dirty' : 'clean'}</div>
{hasErrors && <div>Form has errors</div>}
{errors.name && <div id="error_name">{errors.name}</div>}

<div>
<label htmlFor="name">Name:</label>
<input type="text" name="name" id="name" />
</div>

<div>
<label htmlFor="email">Email:</label>
<input type="email" name="email" id="email" />
</div>

<div>
<label htmlFor="role">Role:</label>
<select name="role" id="role">
<option value="user">User</option>
<option value="admin">Admin</option>
<option value="moderator">Moderator</option>
</select>
</div>

<div>
<label>
<input type="checkbox" name="newsletter" />
Subscribe to newsletter
</label>
</div>

<div>
<label>Preferences:</label>
<label>
<input type="radio" name="preferences" value="option1" />
Option 1
</label>
<label>
<input type="radio" name="preferences" value="option2" />
Option 2
</label>
<label>
<input type="radio" name="preferences" value="option3" />
Option 3
</label>
</div>

<div>
<button type="submit">Submit</button>
</div>
</>
)}
</Form>
</div>
)
}
28 changes: 25 additions & 3 deletions packages/svelte/src/components/Form.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@
export let resetOnError: FormComponentProps['resetOnError'] = false
export let resetOnSuccess: FormComponentProps['resetOnSuccess'] = false
export let setDefaultsOnSuccess: FormComponentProps['setDefaultsOnSuccess'] = false
export let defaultValues: FormComponentProps['defaultValues'] = {}

type FormSubmitOptions = Omit<VisitOptions, 'data' | 'onPrefetched' | 'onPrefetching'>

const form = useForm({})
const form = useForm(defaultValues)
let formElement: HTMLFormElement
let isDirty = false
let defaultData: FormData = new FormData()
Expand All @@ -58,6 +59,28 @@
return formDataToObject(getFormData())
}

function initializeDefaultData() {
if (formElement) {
defaultData = getFormData()
Object.keys(defaultValues).forEach((key) => applyDefaultValue(key, defaultValues[key]))
// Reset the form to ensure default values are applied to the actual form fields
reset()
}
}

function applyDefaultValue(key: string, value: any) {
if (typeof value === 'boolean') {
if (value === true) {
defaultData.set(key, 'on')
} else {
defaultData.delete(key)
}
return
}

defaultData.set(key, value)
}

function updateDirtyState(event: Event) {
isDirty = event.type === 'reset' ? false : !isEqual(getData(), formDataToObject(defaultData))
}
Expand Down Expand Up @@ -162,8 +185,7 @@
}

onMount(() => {
defaultData = getFormData()

initializeDefaultData()
const formEvents = ['input', 'change', 'reset']
formEvents.forEach((e) => formElement.addEventListener(e, updateDirtyState))

Expand Down
71 changes: 71 additions & 0 deletions packages/svelte/test-app/Pages/FormComponent/DefaultValues.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<script lang="ts">
import { Form } from '@inertiajs/svelte'
</script>

<h1>Form with Default Values</h1>

<Form
action="/dump/post"
method="post"
defaultValues={{
name: 'John Doe',
email: '[email protected]',
role: 'admin',
newsletter: true,
preferences: 'option2',
}}
let:isDirty
let:hasErrors
let:errors
>
<!-- State display for testing -->
<div>Form is {isDirty ? 'dirty' : 'clean'}</div>
{#if hasErrors}<div>Form has errors</div>{/if}
{#if errors.name}<div id="error_name">{errors.name}</div>{/if}

<div>
<label for="name">Name:</label>
<input type="text" name="name" id="name" />
</div>

<div>
<label for="email">Email:</label>
<input type="email" name="email" id="email" />
</div>

<div>
<label for="role">Role:</label>
<select name="role" id="role">
<option value="user">User</option>
<option value="admin">Admin</option>
<option value="moderator">Moderator</option>
</select>
</div>

<div>
<label>
<input type="checkbox" name="newsletter" />
Subscribe to newsletter
</label>
</div>

<div>
<label>Preferences:</label>
<label>
<input type="radio" name="preferences" value="option1" />
Option 1
</label>
<label>
<input type="radio" name="preferences" value="option2" />
Option 2
</label>
<label>
<input type="radio" name="preferences" value="option3" />
Option 3
</label>
</div>

<div>
<button type="submit">Submit</button>
</div>
</Form>
32 changes: 29 additions & 3 deletions packages/vue3/src/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,13 @@ const Form: InertiaForm = defineComponent({
type: [String, Array] as PropType<FormComponentProps['invalidateCacheTags']>,
default: () => [],
},
defaultValues: {
type: Object as PropType<FormComponentProps['defaultValues']>,
default: () => ({}),
},
},
setup(props, { slots, attrs, expose }) {
const form = useForm<Record<string, any>>({})
const form = useForm<Record<string, any>>(props.defaultValues)
const formElement = ref()
const method = computed(() =>
typeof props.action === 'object' ? props.action.method : (props.method.toLowerCase() as Method),
Expand All @@ -122,6 +126,28 @@ const Form: InertiaForm = defineComponent({

const defaultData = ref(new FormData())

const initializeDefaultData = () => {
if (formElement.value) {
defaultData.value = getFormData()
Object.keys(props.defaultValues).forEach((key) => applyDefaultValue(key, props.defaultValues[key]))
// Reset the form to ensure default values are applied to the actual form fields
reset()
}
}

const applyDefaultValue = (key: string, value: any) => {
if (typeof value === 'boolean') {
if (value === true) {
defaultData.value.set(key, 'on')
} else {
defaultData.value.delete(key)
}
return
}

defaultData.value.set(key, value)
}

const onFormUpdate = (event: Event) => {
// If the form is reset, we set isDirty to false as we already know it's back
// to defaults. Also, the fields are updated after the reset event, so the
Expand All @@ -132,8 +158,8 @@ const Form: InertiaForm = defineComponent({
const formEvents: Array<keyof HTMLElementEventMap> = ['input', 'change', 'reset']

onMounted(() => {
defaultData.value = getFormData()
formEvents.forEach((e) => formElement.value.addEventListener(e, onFormUpdate))
initializeDefaultData()
formEvents.forEach((e) => formElement.value?.addEventListener(e, onFormUpdate))
})

onBeforeUnmount(() => formEvents.forEach((e) => formElement.value?.removeEventListener(e, onFormUpdate)))
Expand Down
73 changes: 73 additions & 0 deletions packages/vue3/test-app/Pages/FormComponent/DefaultValues.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<template>
<div>
<h1>Form with Default Values</h1>

<Form
action="/dump/post"
method="post"
:default-values="{
name: 'John Doe',
email: '[email protected]',
role: 'admin',
newsletter: true,
preferences: 'option2',
}"
#default="{ isDirty, hasErrors, errors }"
>
<!-- State display for testing -->
<div>Form is <span v-if="isDirty">dirty</span><span v-else>clean</span></div>
<div v-if="hasErrors">Form has errors</div>
<div v-if="errors.name" id="error_name">{{ errors.name }}</div>

<div>
<label for="name">Name:</label>
<input type="text" name="name" id="name" />
</div>

<div>
<label for="email">Email:</label>
<input type="email" name="email" id="email" />
</div>

<div>
<label for="role">Role:</label>
<select name="role" id="role">
<option value="user">User</option>
<option value="admin">Admin</option>
<option value="moderator">Moderator</option>
</select>
</div>

<div>
<label>
<input type="checkbox" name="newsletter" />
Subscribe to newsletter
</label>
</div>

<div>
<label>Preferences:</label>
<label>
<input type="radio" name="preferences" value="option1" />
Option 1
</label>
<label>
<input type="radio" name="preferences" value="option2" />
Option 2
</label>
<label>
<input type="radio" name="preferences" value="option3" />
Option 3
</label>
</div>

<div>
<button type="submit">Submit</button>
</div>
</Form>
</div>
</template>

<script setup lang="ts">
import { Form } from '@inertiajs/vue3'
</script>
Loading