useField on array fields return array length as value and row[] showing only IDs
Link to reproduction
No response
Environment Info
Binaries:
Node: 20.15.1
npm: 10.7.0
Yarn: 1.22.22
pnpm: 9.9.0
Relevant Packages:
payload: 3.0.0-beta.101
next: 15.0.0-canary.104
@payloadcms/db-mongodb: 3.0.0-beta.101
@payloadcms/email-nodemailer: 3.0.0-beta.101
@payloadcms/email-resend: 3.0.0-beta.101
@payloadcms/graphql: 3.0.0-beta.101
@payloadcms/next/utilities: 3.0.0-beta.101
@payloadcms/plugin-cloud: 3.0.0-beta.101
@payloadcms/plugin-cloud-storage: 3.0.0-beta.101
@payloadcms/richtext-lexical: 3.0.0-beta.101
@payloadcms/storage-s3: 3.0.0-beta.101
@payloadcms/translations: 3.0.0-beta.101
@payloadcms/ui/shared: 3.0.0-beta.101
react: 19.0.0-rc-06d0b89e-20240801
react-dom: 19.0.0-rc-06d0b89e-20240801
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 23.6.0: Mon Jul 29 21:14:30 PDT 2024; root:xnu-10063.141.2~1/RELEASE_ARM64_T6000
Available memory (MB): 65536
Available CPU cores: 10
Describe the Bug
Lets say i have such a field of type array and a text field inside
{
name: 'headlines',
label: 'Headlines',
type: 'array',
fields: [
{
name: 'headline',
type: 'text',
required: false,
},
],
},
In a custom component i want to set new headlines.
const {value, rows, setValue } = useField({ path: 'headlines', hasRows: true })
Now the problem is that value is just the length of the array and rows is an array of Ids but missing the actual headline data. I guess thats not suppose to happen. There is no info about this in the docs.
Reproduction Steps
create such a field
{
name: 'headlines',
label: 'Headlines',
type: 'array',
fields: [
{
name: 'headline',
type: 'text',
required: false,
},
],
},
and try to get the data in a custom field component
Adapters and Plugins
No response
+1
I'm using Payload 2.0.0 and have come across this today. When logging the value returned from useField, it simply returns the length of the array
I just faced the same issue and lost several hours attempting to solve it via trial and error as there doesn't seem to be any documentation on the subject. I believe the correct way to do it is like this:
import { useField } from 'payload/components/forms';
const CustomComponent = (props) => {
// value is only the amount of entries when used with arrays!
const { value, setValue } = useField({ path: props.path });
// create a new array to hold the actual values
const arrayValues = new Array();
for(let i = 0; i < value; i += 1) {
// to get the real values you must call useField again on the full path name,
// in your case:
// useField({ path: 'headlines.0.headline' });
// useField({ path: 'headlines.1.headline' });
// ...
arrayValues.push(useField({ path: `${props.path}.${i}` }).value);
}
console.log({ arrayValues });
}
if you wanted to retain the individual setValue functions you could get access to them as well however in my use case I only call the main one once with all the updated data.
What finally clued me in is was to log the output of the useAllFormFields() method which will list out all the forms on the page (which is not what we want). However in doing so you see the way payload manages their names internally.
yea i even have a nested array which has paths like this "outline.0.subheadings.0.subheading" I would need to write a nested loop to get all the data... too much of a hustle so i switched to do the job in the backend instead
why not just give us the data directly in the root object ???
Yeah, it's too bad but I'm guessing they do it that way to maintain their own forms integrity, which are very nice and cover most use-cases.
Still the same issue with payload 3.. the hooks are poorly described. I'm trying to have a button in the UI, that on click is loading data from an API and filling an entry array with the data, but merging it with existing data. Kind of really hard task with these hooks
@DaveSeidman, @JannikZed You can use reduceFieldsToValues to get the data in the classic view:
https://payloadcms.com/docs/admin/hooks#useallformfields
const [fields, dispatchFields] = useAllFormFields();
const formData = reduceFieldsToValues(fields, true);
It works since v1.
Interesting, I saw the warning on this one: "use this hook only if you absolutely need to" and decided not to try it. 😆
If you don't mind, could you please explain how I could leverage it inside my custom component? The component gives the editor a visual interface for positioning "groups" inside a container by dragging and resizing them with their pointer effectively updating their top, left, width, and height values.
My initial hope was that each "group" could hold it's own positional data, but I couldn't get them to save using the basic useField hook, so I ended up carrying a separate field that stringified the array of positions:
{
// preference would be for each group to have a "layout" field: { top: number, left: number, width: number, height: number } that the LayoutComponent could write to
name: 'groups',
type: 'relationship',
relationTo: 'groups',
hasMany: true,
},
{
// instead carry a stringified array alongside the groups array
name: 'layout',
type: 'text',
admin: {
components: {
Field: LayoutComponent,
}
}
}
This works and stays in sync as the user adds and removes groups, but it didn't feel like the most elegant solution. Thanks in advance for your help, Payload is a wonderful platform!
Yeah what you are asking would require us to duplicate data - which we do not want to do. You will need to create the paths that you want to populate and once you have the full path you could use the getDataByPath function exposed in the useForm hook.
I think creating a function that can do this recursively would be your best option here. If you want to contribute to core you could attempt to create a function inside the form provider and expose that function via context.
This issue has been automatically locked. Please open a new issue if this issue persists with any additional detail.