Skip to content

Commit

Permalink
feat(useDataValue): add toOutput, fromExternal and validateRequired (#…
Browse files Browse the repository at this point in the history
…3109)

For data transformation, add these hooks:

- `toOutput`
- `fromExternal`

For custom control about validating the required prop:

- `validateRequired`

Also, adding docs about the `useDataValue` hook.
  • Loading branch information
tujoworker authored Dec 19, 2023
1 parent 25d276c commit cf04125
Show file tree
Hide file tree
Showing 4 changed files with 522 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,152 @@ You can also use a [FieldBlock](/uilib/extensions/forms/create-component/FieldBl
### useDataValue

The `useDataValue` hook standardize handling of the value flow for a single consumer component representing one data point. It holds error state, hides it while the field is in focus, connects to surrounding `DataContext` (if present) and other things that all field or value components needs to do. By implementing custom field or value components and passing the received props through `useDataValue`, all these features work the same way as other field or value components, and you only need to implement the specific unique features of that component.

How to use:

```ts
const { value } = useDataValue(componentProps)
```

Advanced usage:

```ts
const {
value,
onChange,
onFocus,
onBlur,
error,
hasError,
isChanged,
setHasFocus,
} = useDataValue({
toEvent,
errorMessages,
...componentProps,
})
```

#### useDataValue return parameters

- `error` the error object, in case an error is invoked. Will skip returning the error object, if the hook is used in a nested [FieldBlock](/uilib/extensions/forms/create-component/FieldBlock/).

- `hasError` will return true in case an error, even if the hook is nested in a `FieldBlock`.

- `isChanged` returns `true` if the value has changed with e.g. `handleChange`.

- `setHasFocus` accepts a boolean as value. When called, it will update the internal logic - for event handling and validation. Will re-render the React Hook and its outer component.

#### Validation

- `validateRequired` does allow you to provide a custom logic for how the `required` prop should validate.

```ts
const validateRequired = (
value: Value,
{ emptyValue, required, isChanged },
) => {
return required && value === emptyValue
? new FormError('The value is required', {
validationRule: 'required',
})
: undefined
}

const { error, hasError } = useDataValue({
value: undefined,
required: true,
validateInitially: true,
validateRequired,
errorMessages: {
required: 'Show this when "required" fails!',
},
})
```

##### Validation order

During validation, the different APIs do have a prioritization order and will stop processing further when they match:

1. `require` prop
1. `schema` prop (including `pattern`)
1. `validator` prop

#### Error handling

Validation and error-handling is tight coupled together. When a validation fails, you may use the error-object to handle and show the failures/statuses.

To generate the error-object, `FormError` is used. You can use it as well:

```tsx
import { FormError } from '@dnb/eufemia/extensions/forms'
render(
<Field.String
label="Label"
warning={new FormError("I'm a warning too...")}
/>,
)
```

But when you handle errors via `useDataValue`, you may rather provide an object with messages, which will be used to display the error:

```ts
const { error, hasError } = useDataValue({
required: true,
errorMessages: {
required: 'Show this when "required" fails!',
},
...componentProps,
})
```

In order to invoke an error without a change and blur event, you can use `validateInitially`:

```ts
const { error, hasError } = useDataValue({
value: undefined,
required: true,
validateInitially: true,
errorMessages: {
required: 'Show this when "required" fails!',
},
})
```

#### Event handlers

- `handleFocus()` to call the `onFocus` event.

- `handleBlur()` to call the `onBlur` event.

- `handleChange(value)` to call the `onChange` event. Will update/change the internal value and re-render the React Hook, so will the outer component too.

```ts
handleChange(value, (additionalArgs = null))
```

- `updateValue(value)` to update/change the internal value, without calling any events.

- `forceUpdate()` to re-render the React Hook along with the outer component.

#### Value transformers

The transformers are hooks to transform the value on different stages.

They should return a transformed value: `(value) => value`

- `toInput` transforms the value before it gets returned by the hook:

```ts
const { value } = useDataValue(props)
```

- `fromInput` transforms the value given by `handleChange` before it is used in the further process flow.

```ts
handleChange(value)
```

- `toEvent` transforms the internal value before it gets returned by even callbacks such as `onChange`, `onFocus` and `onBlur`.

- `fromExternal` transforms the given props `value` before any other step gets entered.
Loading

0 comments on commit cf04125

Please sign in to comment.