Simple Repeater data is structured as nested and named array if input component is named
Package
filament/forms
Package Version
v3.2
Laravel Version
v11
Livewire Version
v3
PHP Version
PHP 8.0
Problem description
Simple Repeaters produce simple one-dimensional index arrays as data. However, they only allow one to $set data with index arrays if the input component for the simple repeater is unnamed, e.g. Repeater::make('repeater_name')->simple(TextInput::make('')). Using a nested associative array e.g. $set('repeater_name', [[''=>'my_value1'],[''=>'my_value2']]) won't work or results in [object Object].
Conversely, if the repeater input component is named Repeater::make('repeater_name')->simple(TextInput::make('my_input')), data can only be set using a nested associative array e.g. $set('repeater_name', [['my_input'=>'my_value1'],['my_input'=>'my_value2']]). Using a simple index array creates the required number of fields, but doesn't fill those fields.
For a practical example, I noticed this issue when setting up a custom action to export and import Builder JSON. When exporting, the data produced by the Repeater components was a simple index array for both named and unnamed repeater inputs, but the simple repeater values can only be imported into another Builder instance if the repeater form component was unnamed. Here are my JSON exporters and importers for reference:
Actions::make([
Action::make('exportJson')
->label('Export Flow')
->action(function (?TrialFlow $record) {
$rawFlow = $record->flow;
$filename = $record->name . '-' . date('Y-m-d') . '.json';
// Return a download response
return response()->streamDownload(function () use ($rawFlow) {
echo json_encode($rawFlow, JSON_PRETTY_PRINT);
}, $filename, [
'Content-Type' => 'application/json',
]);
}),
Action::make('importJson')
->label('Import Flow')
->form([
FileUpload::make('flowFile')
->required()
->storeFiles(false)
->acceptedFileTypes(['application/json'])
])
->action(function (array $data, ?TrialFlow $record, Set $set, Get $get) {
$tempFile = $data['flowFile'];
$fileContent = $tempFile->get();
$flowData = json_decode($fileContent, true);
$set('flow', $flowData);
})
])
Expected behavior
What did you expect to happen instead? Simple repeaters should produce as output, and accept as input, a simple unnested index array, as stated in the docs: https://filamentphp.com/docs/3.x/forms/fields/repeater#simple-repeaters-with-one-field.
Steps to reproduce
From the ProductResource in the example repo:
Grid::make('')
->columns(3)
->schema([
ToggleButtons::make('Index type')
->live()
->options([
'index' => "Index array",
'named' => "Nested and named"
])
->default('index')
->afterStateUpdated(function(?string $state, Get $get, Set $set): void {
if ($state === 'index') {
$array = ['abc', 'def', 'ghi'];
$set('named', $array);
$set('unnamed', $array);
} else if ($state === 'named') {
$set('named', [['input'=>'abc'], ['input'=>'def'], ['input'=>'ghi']]);
$set('unnamed', [[''=>'abc'], [''=>'def'], [''=>'ghi']]);
}
}),
Repeater::make('named')
->label('Named Repeater')
->simple(
TextInput::make('input')
->disabled()
),
Repeater::make('unnamed')
->label('Unnamed Repeater')
->simple(
TextInput::make('')
->disabled()
)
]),
Reproduction repository (issue will be closed if this is not valid)
https://github.com/SussexPsychologySoftware/filament-repeater-issue
Relevant log output
To add to this issue:
Any data saved in an in an unnamed simple repeater are not available after the page is refreshed, and gets filled with [object Object] regardless. So, it seems like naming the reapeater input is preferrable as the data can be loaded and filled from the db.
My problem is I need to export and import the Builder JSON, but the simple repeater outputs an index array even when it is named, which means it can't then be filled from the JSON. I don't know how it is filling the named input from the db then - the name must be stored somewhere? Any ideas?
I'm trying to fix this (though I might be out of my depth). Here are some notes from testing workarounds:
- Attaching ->afterStateHydrated(...):
- to a named repeater (on the parent, not the input) causes values to disappear after a page refresh.
- to an unnamed repeater allows values like [{'': val1}, {'': val2}] to persist, but the structure changes when new items are added, so not a viable solution.
- I can't log anything from within afterStateHydrated()
- On an unnamed repeater input, empty fields show up as [] instead of null like in named inputs, expects array|string|null. .
- When using $set, those set repeater inputs don't get UUIDs in the $state.
- afterStateUpdated or afterStateHydrated in child input only fire when using the parent repeater’s add button. Updates lag and show outdated data.
- Adding new items transforms the state:
- Previous key/values are nested.
- New items on Named input: UUID: {name: value} but old items stored with int: {name: value}
- Unnamed input: UUID: value (but value is an empty array for new inputs).
- On the create page, unnamed repeaters often auto-fill with [object Object] and save as {'': null}.
- Create mode seems more buggy than edit.
- Refreshing the page sometimes leads to different results, especially with the JSON imports.
NB: I've done another commit since this bug was accepted so you'd need to pull to test the above.
Hi, this is an intended behaviour - simple repeaters are the same structure as normal repeaters internally, the only real difference is that they have different loading/saving behaviour which transforms their internal values into the correct one-dimensional format. There is not a way for us to change how the internal data structure looks really. You are doing the right thing when using $set or $get by using the multi-dimensional array