Skip to content

Import

import { Form } from '@dnb/eufemia/extensions/forms'
render(<Form.Section />)

Description

Form.Section lets you compose together sections of fields and values to be reused in different contexts.

Relevant links

Good to know

  • It allows you to easily customize and reposition nested Form.Section components within the data context, making it effortless to reuse the same set of fields and values in various contexts. Check out e.g. the overwriteProps and path properties for more information.
  • When defining a default value for a field or value, you can use the defaultValue property instead of value. It will not take precedence over the data context like value does.
  • Use Form.Visibility to handle logic and hide parts of your form based on the data context.
  • To provide localization for all used texts defined in the translations property of the section, you can create a localization easily.
  • Only imported sections with their localizations are included in the production bundle.
  • It is possible to overwrite the translations later by using the Form.Handler component.
  • A single section can be used without Form.Handler, just like any other field and value.
  • An Form.Section.EditContainer and Form.Section.ViewContainer container is available to be used.
  • Here is a list of blocks ready to use.

Usage

  1. Create the section component and export it:
import { Form, Field } from '@dnb/eufemia/extensions/forms'
export function MySection(props) {
return (
<Form.Section {...props}>
{/* Fields*/}
<Field.PhoneNumber path="/phoneNumber" required />
{/* Views*/}
<View.PhoneNumber path="/phoneNumber" />
</Form.Section>
)
}
  1. Import the section component and use it in a form:
import { Form } from '@dnb/eufemia/extensions/forms'
import { MySection } from './form-sections'
function MyForm() {
return (
<Form.Handler>
<MySection />
</Form.Handler>
)
}

Overwrite properties

It lets you overwrite all of the given properties if needed by using overwriteProps:

import { Form, Field } from '@dnb/eufemia/extensions/forms'
const MySection = (props) => {
return (
<Form.Section {...props}>
<Field.Name.First path="/firstName" />
<Field.Name.Last path="/lastName" required minLength={2} />
</Form.Section>
)
}
render(
<Form.Handler>
<MySection
overwriteProps={{
firstName: { label: '/Custom label' },
lastName: { required: false, minLength: 0 },
}}
/>
</Form.Handler>,
)

Optional path support

Optionally you can define a path to the section component. Fields inside the section will get this path as a prefix of their own path. This makes it possible to reuse the same section component in different contexts.

import { Form, Field } from '@dnb/eufemia/extensions/forms'
const MySection = (props) => {
return (
<Form.Section {...props}>
<Field.Name.First path="/firstName" />
<Field.Name.Last path="/lastName" />
</Form.Section>
)
}
render(
<Form.Handler
defaultData={{
// MySection has a path="/nestedPath" and therefore it is nested in the data context
nestedPath: {
firstName: 'Nora',
},
}}
>
<MySection path="/nestedPath" />
</Form.Handler>,
)

Required property

Optionally you can define a required property to the section component. Fields inside the section then be required.

render(
<Form.Section required={true}>
<Field.Name.First path="/firstName" />
<Field.Name.Last path="/lastName" />
</Form.Section>,
)

TypeScript support

Optionally you can define what the properties of the nested fields should accept:

import { Form, Field, SectionProps } from '@dnb/eufemia/extensions/forms'
import type { Props as PhoneNumberProps } from '@dnb/eufemia/extensions/forms/Field/PhoneNumber'
const MySection = (
props: SectionProps<{ phoneNumber?: PhoneNumberProps }>,
) => {
return (
<Form.Section {...props}>
<Field.Name.Last path="/phoneNumber" required />
</Form.Section>
)
}
render(
<Form.Handler>
<MySection />
</Form.Handler>,
)

Schema support

You can also use a schema to define the properties of the nested fields.

Using Zod schemas

import { Form, Field, z } from '@dnb/eufemia/extensions/forms'
const MySection = (props) => {
return (
<Form.Section {...props}>
<Field.PhoneNumber path="/phoneNumber" />
</Form.Section>
)
}
const schema = z.object({
mySection: z.object({
phoneNumber: z.string().regex(/^[0-9]{10}$/),
}),
})
render(
<Form.Handler schema={schema}>
<MySection path="/mySection" />
</Form.Handler>,
)

Using JSON Schema (Ajv)

import {
Form,
Field,
JSONSchema,
makeAjvInstance,
} from '@dnb/eufemia/extensions/forms'
const ajv = makeAjvInstance()
const MySection = (props) => {
return (
<Form.Section {...props}>
<Field.PhoneNumber path="/phoneNumber" />
</Form.Section>
)
}
const schema: JSONSchema = {
type: 'object',
properties: {
mySection: {
type: 'object',
properties: {
phoneNumber: {
type: 'string',
pattern: '^[0-9]{10}$',
},
},
required: ['phoneNumber'],
},
},
}
render(
<Form.Handler schema={schema} ajvInstance={ajv}>
<MySection path="/mySection" />
</Form.Handler>,
)

Translations

You can use the translations property to provide translations for the section:

import { Field, Form } from '@dnb/eufemia/extensions/forms'
const translations = {
// It's optional to wrap the translations in an additional "MySection" object
'nb-NO': { MySection: { MyField: { label: 'Felt label' } } },
'en-GB': { MySection: { MyField: { label: 'Field label' } } },
}
// For TypeScript support
type Translation = (typeof translations)[keyof typeof translations]
export function MySection() {
return (
<Form.Section translations={translations}>
<ContentOfMySection />
</Form.Section>
)
}
function ContentOfMySection() {
const { MyField } = Form.useTranslation<Translation>().MySection
return <Field.String label={MyField.label} path="/custom" />
}

This way it is possible to "extend" or change the translations for a specific section from a Form.handler:

import { Form } from '@dnb/eufemia/extensions/forms'
import { MySection } from './form-sections'
const myTranslations = {
'nb-NO': { MySection: { MyField: { label: 'Egendefinert' } } },
'en-GB': { MySection: { MyField: { label: 'Custom' } } },
}
export function MyForm() {
return (
<Form.Handler translations={myTranslations}>
<MySection />
</Form.Handler>
)
}

Edit and View container

The Form.Section supports an Form.Section.EditContainer and Form.Section.ViewContainer container. The edit container should be used for data input with fields. While the view container is used to display the data in a read-only way.

import { Form, Field } from '@dnb/eufemia/extensions/forms'
function MyEditContainer() {
return (
<Form.Section.EditContainer>
<Field.Name.First path="/firstName" />
<Field.Name.Last path="/lastName" />
</Form.Section.EditContainer>
)
}
function MyViewContainer() {
return (
<Form.Section.EditContainer>
<Value.Name.First path="/firstName" />
<Value.Name.Last path="/lastName" />
</Form.Section.EditContainer>
)
}
function MySection() {
return (
<Form.Section>
<MyEditContainer />
<MyViewContainer />
</Form.Section>
)
}
render(
<Form.Handler>
<MySection />
<Form.SubmitButton />
</Form.Handler>,
)

Note: The reason Done and not Save is used in the toolbar button is because validation is done in fields. If we decouple the data entered in a section, the Form.Handler will not be able to validate the data on submit. This can also lead to missing data because the user can press the submit button before Save is pressed.

Snapshot testing of a section (block)

One way to ensure a "contract" of what you expect a section to be outlined as, is to create a snapshot test:

import { Form, Tools } from '@dnb/eufemia/extensions/forms'
import { GenerateRef } from '@dnb/eufemia/extensions/forms/Tools/ListAllProps'
it('MySection should match snapshot', () => {
const generateRef = React.createRef<GenerateRef>()
render(
<Form.Handler>
<Tools.ListAllProps generateRef={generateRef}>
<MySection />
</Tools.ListAllProps>
</Form.Handler>,
)
const { propsOfFields, propsOfValues } = generateRef.current()
expect(propsOfFields).toMatchInlineSnapshot(`...`)
expect(propsOfValues).toMatchInlineSnapshot(`...`)
})