-
Notifications
You must be signed in to change notification settings - Fork 6
2.3 useDynamicInputs
Provides methods for managing arrays in form data. Use it to make a reusable component with your own buttons and styles.
useDynamicInputs
accepts an object:
{
model: string,
emptyData: Record<string, unknown>
}
Prop | Description |
---|---|
model |
A dot-notation string used to access an array in the form data |
emptyData |
An object with default (probably empty) values to be pushed to the end of the data array when a new input is added |
It returns an object:
{
addInput: (override?: Partial<T> | ((records: T[]) => Partial<T>)) => void,
removeInput: (i: number) => T,
paths: string[],
}
addInput
has 3 call signatures.
-
When passed nothing, it will push a copy of
emptyData
to the form data array. -
When passed an object, it will merge that object with
emptyData
, overriding any existing keys/values. -
When passed a function, it will first call that function, and then merge it with
emptyData
const PhoneInputs = () => {
const { addInput, removeInput, paths } = useDynamicInputs({
model: 'contact.phones',
emptyData: { number: '', type: '', order: 0 }
})
const handleAddInput = () => {
// Pushes { number: '', type: '', order: '' } (from emptyData above) to the phones form data array
addInput()
// Override a value before pushing it to the form data array
addInput({
type: 'personal'
})
// Passing a function allows using the data to build dynamic values before pushing to the form data array
addInput(records => {
const highestOrder = records.reduce((acc, record) => record.order > acc ? record.order : acc, 0)
return {
order: highestOrder + 1
}
})
}
return (
<>
<div style={ { display: 'flex' } }>
<label style={ { flex: 1 } }>{ label }</label>
<button onClick={ handleAddInput }>+</button>
</div>
{ paths.map((path, i) => (
<NestedFields key={ i } model={ path }>
<div style={ { display: 'flex' } }>
<div>{ children }</div>
<button onClick={ onClick: () => removeInput(i) }>-</button>
</div>
</NestedFields>
)) }
</>
)
}
Using useDynamicInputs
you can build a dynamic inputs interface using your own markup structure or FE component framework.
const DynamicInputs = ({ children, model, label, emptyData }) => {
const { addInput, removeInput, paths } = useDynamicInputs({ model, emptyData })
return (
<>
<div style={ { display: 'flex' } }>
<label style={ { flex: 1 } }>{ label }</label>
<button onClick={ addInput }>+</button>
</div>
{ paths.map((path, i) => (
<NestedFields key={ i } model={ path }>
<div style={ { display: 'flex' } }>
<div>{ children }</div>
<button onClick={ onClick: () => removeInput(i) }>-</button>
</div>
</NestedFields>
)) }
</>
)
}
This can then be used inside of a Form component:
const user = {
user: {
username: "bmo",
emails: [
{ email: "bmo@treehouse.ooo", type: "personal" }
]
}
}
const PageWithFormOnIt = () => {
return (
<Form model="user" data={ { user } } to={ `users/${user.id}` } method="patch">
<TextInput name="firstName" label="First Name" />
<DynamicInputs model="emails" emptyData={ { email: '', type: ''} } label="Emails">
<TextInput name="email" label="Email" />
<TextInput name="type" label="Type" />
</DynamicInputs>
</Form>
)
}
A component called DynamicInputs
is exported which implements this if you don't need to customize how the HTML is generated.