/** * External dependencies */ import deepMerge from 'deepmerge'; /** * WordPress dependencies */ import { useCallback, useState } from '@wordpress/element'; /** * Internal dependencies */ import DataForm from '../index'; import type { Field } from '../../types'; const DataAdapterComponent = () => { type DataAdapterItem = { user: { profile: { name: string; email: string; }; preferences: { notifications: boolean; }; }; revenue: { total: number; units: number; pricePerUnit: number; }; }; const [ data, setData ] = useState< DataAdapterItem >( { user: { profile: { name: 'John Doe', email: 'john@example.com', }, preferences: { notifications: true, }, }, revenue: { total: 30, units: 10, pricePerUnit: 3, }, } ); const nestedFields: Field< DataAdapterItem >[] = [ // Examples of autogenerated getValue/setValue methods // for nested data based on the field id. { id: 'user.profile.name', label: 'User Name', type: 'text', }, { id: 'user.profile.email', label: 'User Email', type: 'email', }, { id: 'user.profile.tags', label: 'User Tags', type: 'array', placeholder: 'Enter comma-separated tags', description: 'Add tags separated by commas (e.g., "tag1, tag2, tag3")', elements: [ { value: 'astronomy', label: 'Astronomy' }, { value: 'book-review', label: 'Book review' }, { value: 'event', label: 'Event' }, { value: 'photography', label: 'Photography' }, { value: 'travel', label: 'Travel' }, ], }, // Example of adapting a data value to a control value // by providing getValue/setValue methods. { id: 'user.preferences.notifications', label: 'Notifications', type: 'boolean', Edit: 'radio', elements: [ { label: 'Enabled', value: 'enabled' }, { label: 'Disabled', value: 'disabled' }, ], getValue: ( { item } ) => item.user.preferences.notifications === true ? 'enabled' : 'disabled', setValue: ( { value } ) => ( { user: { preferences: { notifications: value === 'enabled' }, }, } ), }, // Example of deriving data by leveraging setValue method. { id: 'revenue.total', label: 'Total Revenue', type: 'integer', readOnly: true, }, { id: 'revenue.pricePerUnit', label: 'Price Per Unit', type: 'integer', setValue: ( { item, value } ) => ( { revenue: { total: value * item.revenue.units, pricePerUnit: value, }, } ), }, { id: 'revenue.units', label: 'Units', type: 'integer', setValue: ( { item, value } ) => ( { revenue: { total: item.revenue.pricePerUnit * value, units: value, }, } ), }, ]; const handleChange = useCallback( ( edits: any ) => { // Edits will respect the shape of the data // because fields provide the proper information // (via field.id or via field.setValue). setData( ( prev ) => deepMerge( prev, edits, { arrayMerge: ( target, source ) => source, } ) ); }, [] ); return ( <>
This story is best looked at with the code on the side. It aims to highlight how DataForm can wrangle data in scenarios such as nested data, bridge data to/from UI controls, and derived data.
Current data snapshot:
{ JSON.stringify( data, null, 2 ) }
The first example demonstrates how to signal nested data via{ ' ' }
field.id.
By using { `{ id: 'user.profile.name' }` } as field
id, when users edit the name, the edits will come in this shape:
{ `{ user: { profile: { name: 'John Doe' } } }` }
Sometimes, we need to adapt the data type to and from the UI control response. This example demonstrates how to adapt a boolean to a text string (Enabled/Disabled).
Last, but not least, this example showcases how to work with derived data by providing a custom setValue function. Note how, changing UNITS or PRICE PER UNIT, updates the TOTAL value as well.