diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useSnapshot.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useSnapshot.mdx
new file mode 100644
index 00000000000..41f3a26d950
--- /dev/null
+++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useSnapshot.mdx
@@ -0,0 +1,23 @@
+---
+title: 'useSnapshot'
+description: '`Form.useSnapshot` lets you store data snapshots of your form data, either inside or outside of the form context.'
+showTabs: true
+tabs:
+ - title: Info
+ key: '/info'
+ - title: Demos
+ key: '/demos'
+breadcrumb:
+ - text: Forms
+ href: /uilib/extensions/forms/
+ - text: Form
+ href: /uilib/extensions/forms/Form/
+ - text: Form.useSnapshot
+ href: /uilib/extensions/forms/Form/useSnapshot/
+---
+
+import Info from 'Docs/uilib/extensions/forms/Form/useSnapshot/info'
+import Demos from 'Docs/uilib/extensions/forms/Form/useSnapshot/demos'
+
+
+
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useSnapshot/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useSnapshot/Examples.tsx
new file mode 100644
index 00000000000..e80949d0204
--- /dev/null
+++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useSnapshot/Examples.tsx
@@ -0,0 +1,100 @@
+import ComponentBox from '../../../../../../shared/tags/ComponentBox'
+import { Field, Form, Wizard } from '@dnb/eufemia/src/extensions/forms'
+
+export const InWizard = () => {
+ return (
+
+ {() => {
+ const MyForm = () => {
+ const { createSnapshot, revertSnapshot } =
+ Form.useSnapshot('my-form')
+
+ return (
+
+ {
+ if (mode !== 'previous') {
+ createSnapshot(args.id)
+ }
+ }}
+ onBeforeStepChange={(index, mode, args) => {
+ if (mode === 'previous') {
+ revertSnapshot(args.id)
+ }
+ }}
+ >
+
+ Step A
+
+
+
+
+
+ Step B
+
+
+
+
+
+ ['group-1', 'group-2'].includes(value),
+ }}
+ >
+ Step C
+
+
+
+
+
+ Step D
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+
+ return
+ }}
+
+ )
+}
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useSnapshot/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useSnapshot/demos.mdx
new file mode 100644
index 00000000000..fc99aa80054
--- /dev/null
+++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useSnapshot/demos.mdx
@@ -0,0 +1,13 @@
+---
+showTabs: true
+---
+
+import * as Examples from './Examples'
+
+## Demos
+
+### Used in a Wizard
+
+This example revers the form data to its previous state when the user navigates back to a previous step.
+
+
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useSnapshot/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useSnapshot/info.mdx
new file mode 100644
index 00000000000..2122a43198d
--- /dev/null
+++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useSnapshot/info.mdx
@@ -0,0 +1,75 @@
+---
+showTabs: true
+---
+
+## Description
+
+The `Form.useSnapshot` hook lets you store data snapshots of your form data, either inside or outside of the form context.
+
+The hook returns an object with the following properties:
+
+```tsx
+import { Form } from '@dnb/eufemia/extensions/forms'
+
+function MyComponent() {
+ const { createSnapshot, revertSnapshot } = Form.useSnapshot()
+
+ return <>MyComponent>
+}
+
+render(
+
+
+ ,
+)
+```
+
+- `createSnapshot` will store the current data as a new snapshot with the given id.
+- `revertSnapshot` will revert the data to the snapshot with the given id (required). A reverted snapshot gets deleted from the memory.
+
+## Usage
+
+You can use the `Form.useSnapshot` hook with or without an `id` (string) property, which is optional and can be used to link the data to a specific [Form.Handler](/uilib/extensions/forms/Form/Handler/) component.
+
+### Without an `id` property
+
+Here "Component" is rendered inside the `Form.Handler` component and does not need an `id` property to access the form data:
+
+```jsx
+import { Form } from '@dnb/eufemia/extensions/forms'
+
+function MyForm() {
+ return (
+
+
+
+ )
+}
+
+function Component() {
+ const { createSnapshot, revertSnapshot } = Form.useSnapshot()
+}
+```
+
+### With an `id` property
+
+While in this example, "Component" is outside the `Form.Handler` context, but linked together via the `id` (string) property:
+
+```jsx
+import { Form } from '@dnb/eufemia/extensions/forms'
+
+function MyForm() {
+ return (
+ <>
+ ...
+
+ >
+ )
+}
+
+function Component() {
+ const { createSnapshot, revertSnapshot } = Form.useSnapshot('unique')
+}
+```
+
+This is beneficial when you need to utilize the form data in other places within your application.
diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/index.ts b/packages/dnb-eufemia/src/extensions/forms/Form/index.ts
index ae7fd03bd51..302c50df54b 100644
--- a/packages/dnb-eufemia/src/extensions/forms/Form/index.ts
+++ b/packages/dnb-eufemia/src/extensions/forms/Form/index.ts
@@ -18,6 +18,7 @@ export { default as getData } from './data-context/getData'
export { default as clearData } from './data-context/clearData'
export { default as useValidation } from './data-context/useValidation'
export { default as useTranslation } from '../hooks/useTranslation'
+export { default as useSnapshot } from '../hooks/useSnapshot'
/**
* Can be removed in v11
diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/stories/Wizard.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Wizard/stories/Wizard.stories.tsx
index c1572f941cc..2598e7b628c 100644
--- a/packages/dnb-eufemia/src/extensions/forms/Wizard/stories/Wizard.stories.tsx
+++ b/packages/dnb-eufemia/src/extensions/forms/Wizard/stories/Wizard.stories.tsx
@@ -42,8 +42,9 @@ export const Basic = () => {
}
export const WizardDynamicStepsActiveWhen = () => {
+ const { createSnapshot, revertSnapshot } = Form.useSnapshot('my-form')
return (
-
+
{
console.log(
@@ -53,23 +54,40 @@ export const WizardDynamicStepsActiveWhen = () => {
args.id,
args.previousIndex
)
+ if (mode !== 'previous') {
+ createSnapshot(args.id)
+ }
+ }}
+ onBeforeStepChange={(index, mode, args) => {
+ console.log(
+ 'onBeforeStepChange',
+ index,
+ mode,
+ args.id,
+ args.previousIndex
+ )
+ if (mode === 'previous') {
+ revertSnapshot(args.id)
+ }
}}
>
Step A
+
Step B
+
@@ -78,20 +96,22 @@ export const WizardDynamicStepsActiveWhen = () => {
activeWhen={{
path: '/activeSteps',
hasValue: (value: string) =>
- ['group-1', 'group-2'].includes(value),
+ ['group-a', 'group-b'].includes(value),
}}
id="step-c"
>
Step C
+
Step D
+
@@ -102,8 +122,8 @@ export const WizardDynamicStepsActiveWhen = () => {
optionsLayout="horizontal"
top
>
-
-
+
+
)
diff --git a/packages/dnb-eufemia/src/extensions/forms/hooks/__tests__/useSnapshot.test.ts b/packages/dnb-eufemia/src/extensions/forms/hooks/__tests__/useSnapshot.test.ts
new file mode 100644
index 00000000000..103bc1babcc
--- /dev/null
+++ b/packages/dnb-eufemia/src/extensions/forms/hooks/__tests__/useSnapshot.test.ts
@@ -0,0 +1,68 @@
+import { renderHook, act } from '@testing-library/react'
+import useSnapshot from '../useSnapshot'
+
+let mockData: any
+const mockSet = jest.fn()
+
+beforeEach(() => {
+ mockData = { value: 'initial' }
+ mockSet.mockClear()
+})
+
+afterEach(() => {
+ jest.restoreAllMocks()
+})
+
+jest.mock('../../Form/data-context/useData', () => {
+ const useDataMock = jest.fn(() => ({
+ data: mockData,
+ set: mockSet,
+ }))
+ return useDataMock
+})
+
+describe('Form.useSnapshot', () => {
+ it('creates a snapshot and retrieves it correctly', () => {
+ const { result } = renderHook(useSnapshot)
+ let snapshotId: string
+
+ act(() => {
+ snapshotId = result.current.createSnapshot()
+ })
+
+ expect(result.current.createSnapshot).toBeDefined()
+ expect(result.current.revertSnapshot).toBeDefined()
+ expect(snapshotId).toBeTruthy()
+ })
+
+ it('reverts to a snapshot', () => {
+ const { result } = renderHook(useSnapshot)
+ act(() => {
+ const snapshotId = result.current.createSnapshot(undefined, {
+ value: 'modified',
+ })
+ mockSet.mockClear()
+ result.current.revertSnapshot(snapshotId)
+ })
+
+ expect(mockSet).toHaveBeenCalledWith({ value: 'modified' })
+ })
+
+ it('reverts and deletes the snapshot', () => {
+ const { result } = renderHook(useSnapshot)
+ let snapshotId: string
+
+ act(() => {
+ snapshotId = result.current.createSnapshot()
+ result.current.revertSnapshot(snapshotId)
+ })
+
+ expect(mockSet).toHaveBeenCalledWith(mockData)
+
+ mockSet.mockClear()
+ act(() => {
+ result.current.revertSnapshot(snapshotId)
+ })
+ expect(mockSet).not.toHaveBeenCalled()
+ })
+})
diff --git a/packages/dnb-eufemia/src/extensions/forms/hooks/useSnapshot.tsx b/packages/dnb-eufemia/src/extensions/forms/hooks/useSnapshot.tsx
new file mode 100644
index 00000000000..231074f6dce
--- /dev/null
+++ b/packages/dnb-eufemia/src/extensions/forms/hooks/useSnapshot.tsx
@@ -0,0 +1,43 @@
+import { useCallback, useRef } from 'react'
+import { makeUniqueId } from '../../../shared/component-helper'
+import { SharedStateId } from '../../../shared/helpers/useSharedState'
+import useData from '../Form/data-context/useData'
+
+export default function useSnapshot(id?: SharedStateId) {
+ const snapshotsRef = useRef