Skip to content

Commit

Permalink
feat(Forms): add support for using a function references instead of a…
Browse files Browse the repository at this point in the history
… string based id (#4331)

This feature allows developers to use a function or a React Context as
the reference instead of a string-based ID. This can be useful for
ensuring a safe local scope, especially when multiple form handlers are
present.

```tsx
const myReference= () => null

const MyField = () => {
  const { data } = Form.useData(myReference)
  return data.foo
} 

render(
<>
  <Form.Handler id={myReference}>
    ...
  </Form.Handler>
  
  <MyField />
</>
)
```
tujoworker authored Nov 25, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 69dc60a commit a6e3bc3
Showing 21 changed files with 386 additions and 77 deletions.
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ const { getValue, data, filterData, reduceToVisibleFields } =
- `filterData` will filter the data based on your own logic.
- `reduceToVisibleFields` will reduce the given data set to only contain the visible fields (mounted fields).

You link them together via the `id` (string) property.
You link them together via the `id` (string, function, object or React Context as the reference) property.

TypeScript support:

Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ function Component() {
}
```

You link them together via the `id` (string) property.
You link them together via the `id` (string, function, object or React Context as the reference) property.

Related helpers:

Original file line number Diff line number Diff line change
@@ -42,7 +42,7 @@ render(

## Usage

You can use the `Form.useData` 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.
You can use the `Form.useData` hook with or without an `id` (string, function, object or React Context as the reference) 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

@@ -66,7 +66,7 @@ function Component() {

### With an `id` property

While in this example, "Component" is outside the `Form.Handler` context, but linked together via the `id` (string) property:
While in this example, "Component" is outside the `Form.Handler` context, but linked together via the `id` (string, function, object or React Context as the reference) property:

```jsx
import { Form } from '@dnb/eufemia/extensions/forms'
Original file line number Diff line number Diff line change
@@ -61,7 +61,7 @@ You can check out examples in the demo section.

## Usage of the `Form.useSnapshot` hook

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.
You can use the `Form.useSnapshot` hook with or without an `id` (string, function, object or React Context as the reference) 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

@@ -85,7 +85,7 @@ function Component() {

### With an `id` property

While in this example, "Component" is outside the `Form.Handler` context, but linked together via the `id` (string) property:
While in this example, "Component" is outside the `Form.Handler` context, but linked together via the `id` (string, function, object or React Context as the reference) property:

```jsx
import { Form } from '@dnb/eufemia/extensions/forms'
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@ function Component() {
}
```

Or by linking the hook together with the form by using the `id` (string) property:
Or by linking the hook together with the form by using the `id` (string, function, object or React Context as the reference) property:

```jsx
import { Form } from '@dnb/eufemia/extensions/forms'
Original file line number Diff line number Diff line change
@@ -117,7 +117,7 @@ const MyForm = () => {
}
```

When using the `useStep` hook outside of the `Wizard.Container` context, you need to provide an unique `id` (string):
When using the `useStep` hook outside of the `Wizard.Container` context, you need to provide an unique `id` (string, function, object or React Context as the reference):

```tsx
import { Form, Wizard } from '@dnb/eufemia/extensions/forms'
Original file line number Diff line number Diff line change
@@ -106,7 +106,7 @@ function MyForm() {

## Without a router

You connect the hook with the `Wizard.Container` component via an unique `id` (string). The `id` will be used in the URL query string: `url?unique-id-step=1`.
You connect the hook with the `Wizard.Container` component via an unique `id` (string, function, object or React Context as the reference). The `id` will be used in the URL query string: `url?unique-id-step=1`.

```jsx
import { Form, Wizard } from '@dnb/eufemia/extensions/forms'
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ function MyForm() {
}
```

You can also connect the hook with the `Wizard.Container` via an `id` (string). This lets you render the hook outside of the context:
You can also connect the hook with the `Wizard.Container` via an `id` (string, function, object or React Context as the reference). This lets you render the hook outside of the context:

```jsx
import { Form } from '@dnb/eufemia/extensions/forms'
Original file line number Diff line number Diff line change
@@ -128,9 +128,11 @@ Here is an example of how to use these methods:
```jsx
import { Form } from '@dnb/eufemia/extensions/forms'

const myFormId = 'unique-id' // or a function, object or React Context reference

function MyForm() {
return (
<Form.Handler id="unique-id">
<Form.Handler id={myFormId}>
<MyComponent />
</Form.Handler>
)
@@ -145,15 +147,15 @@ function MyComponent() {
data,
filterData,
reduceToVisibleFields,
} = Form.useData() // optionally provide an id (unique-id)
} = Form.useData() // optionally provide an id or reference
}

// You can also use the setData:
Form.setData('unique-id', { companyName: 'DNB' })
Form.setData(myFormId, { companyName: 'DNB' })

// ... and the getData – method when ever you need to:
const { getValue, data, filterData, reduceToVisibleFields } =
Form.getData('unique-id')
Form.getData(myFormId)
```

- `getValue` will return the value of the given path.
Original file line number Diff line number Diff line change
@@ -7,14 +7,14 @@ import {
Path,
EventStateObject,
EventReturnWithStateObject,
Identifier,
FieldProps,
ValueProps,
OnChange,
OnSubmitParams,
} from '../types'
import { Props as ProviderProps } from './Provider'
import { SnapshotName } from '../Form/Snapshot'
import { SharedStateId } from '../../../shared/helpers/useSharedState'

export type MountState = {
isPreMounted?: boolean
@@ -85,7 +85,7 @@ export type FieldConnections = {
}

export interface ContextState {
id?: Identifier
id?: SharedStateId
hasContext: boolean
/** The dataset for the form / form wizard */
data: any
Original file line number Diff line number Diff line change
@@ -33,7 +33,11 @@ import { debounce } from '../../../../shared/helpers'
import FieldPropsProvider from '../../Field/Provider'
import useUpdateEffect from '../../../../shared/helpers/useUpdateEffect'
import { isAsync } from '../../../../shared/helpers/isAsync'
import { useSharedState } from '../../../../shared/helpers/useSharedState'
import {
SharedStateId,
createReferenceKey,
useSharedState,
} from '../../../../shared/helpers/useSharedState'
import SharedContext, { ContextProps } from '../../../../shared/Context'
import useTranslation from '../../hooks/useTranslation'
import DataContext, {
@@ -74,7 +78,7 @@ export interface Props<Data extends JsonObject>
/**
* Unique ID to communicate with the hook Form.useData
*/
id?: string
id?: SharedStateId
/**
* Unique ID to connect with a GlobalStatus
*/
@@ -618,10 +622,10 @@ export default function Provider<Data extends JsonObject>(
// - Shared state
const sharedData = useSharedState<Data>(id)
const sharedAttachments = useSharedState<SharedAttachments<Data>>(
id + '-attachments'
createReferenceKey(id, 'attachments')
)
const sharedDataContext = useSharedState<ContextState>(
id + '-data-context'
createReferenceKey(id, 'data-context')
)

const setSharedData = sharedData.set
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ export const ProviderProperties: PropertiesTableProps = {
},
id: {
doc: 'Unique id for connecting Form.Handler and helper tools such as Form.useData.',
type: 'string',
type: ['string', 'Function', 'Object', 'React.Context'],
status: 'optional',
},
schema: {
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import React from 'react'
import { fireEvent, render } from '@testing-library/react'
import { Form, DataContext, Field } from '../../..'
@@ -151,4 +152,17 @@ describe('Form.Element', () => {
expect(attributes).toEqual(['class', 'aria-label'])
expect(formElement.getAttribute('aria-label')).toBe('Aria Label')
})

it('should ensure that only a string can be set as the id', () => {
const myId = () => null
render(
// @ts-expect-error
<Form.Element id={myId}>
<Form.SubmitButton>Submit</Form.SubmitButton>
</Form.Element>
)

const formElement = document.querySelector('form')
expect(formElement).not.toHaveAttribute('id')
})
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { createContext } from 'react'
import { renderHook, act, render, fireEvent } from '@testing-library/react'
import { makeUniqueId } from '../../../../../shared/component-helper'
import { Field, Form, Wizard } from '../../..'
@@ -124,6 +124,89 @@ describe('Form.useData', () => {
expect(result.current.data).toEqual({ key: 'changed value' })
})

it('should get data with a string as the id', () => {
const { result } = renderHook(() => useData(identifier), {
wrapper: ({ children }) => (
<>
<Provider id={identifier}>
<Field.String path="/foo" defaultValue="foo" />
<Field.String path="/bar" defaultValue="bar" />
</Provider>

{children}
</>
),
})

expect(result.current.data).toEqual({
foo: 'foo',
bar: 'bar',
})
})

it('should get data with a function reference as the id', () => {
const myId = () => null
const { result } = renderHook(() => useData(myId), {
wrapper: ({ children }) => (
<>
<Provider id={myId}>
<Field.String path="/foo" defaultValue="foo" />
<Field.String path="/bar" defaultValue="bar" />
</Provider>

{children}
</>
),
})

expect(result.current.data).toEqual({
foo: 'foo',
bar: 'bar',
})
})

it('should get data with an object reference as the id', () => {
const myId = {}
const { result } = renderHook(() => useData(myId), {
wrapper: ({ children }) => (
<>
<Provider id={myId}>
<Field.String path="/foo" defaultValue="foo" />
<Field.String path="/bar" defaultValue="bar" />
</Provider>

{children}
</>
),
})

expect(result.current.data).toEqual({
foo: 'foo',
bar: 'bar',
})
})

it('should get data with a React Context as the id', () => {
const myId = createContext(null)
const { result } = renderHook(() => useData(myId), {
wrapper: ({ children }) => (
<>
<Provider id={myId}>
<Field.String path="/foo" defaultValue="foo" />
<Field.String path="/bar" defaultValue="bar" />
</Provider>

{children}
</>
),
})

expect(result.current.data).toEqual({
foo: 'foo',
bar: 'bar',
})
})

describe('remove', () => {
it('should remove the data', () => {
const { result } = renderHook(() => useData(), {
@@ -177,6 +260,35 @@ describe('Form.useData', () => {
})
expect(result.current.data).not.toHaveProperty('foo')
})

it('should remove data with handler id', () => {
const { result } = renderHook(() => useData(identifier), {
wrapper: ({ children }) => (
<>
<Provider id={identifier}>
<Field.String path="/foo" defaultValue="foo" />
<Field.String path="/bar" defaultValue="bar" />
</Provider>

{children}
</>
),
})

expect(result.current.data).toEqual({
foo: 'foo',
bar: 'bar',
})

act(() => {
result.current.remove('/foo')
})

expect(result.current.data).toEqual({
bar: 'bar',
})
expect(result.current.data).not.toHaveProperty('foo')
})
})

it('"update" should only re-render when value has changed', () => {
@@ -225,40 +337,71 @@ describe('Form.useData', () => {
expect(result.current.data).toEqual({ key: 'changed value' })
})

it('should sync two hooks by using "update"', () => {
const props = { key: 'value' }
describe('update', () => {
it('should sync two hooks by using "update"', () => {
const props = { key: 'value' }

const { result: A } = renderHook(() => useData(identifier))
const { result: B } = renderHook(() => useData(identifier, props))
const { result: A } = renderHook(() => useData(identifier))
const { result: B } = renderHook(() => useData(identifier, props))

expect(A.current.data).toEqual({ key: 'value' })
expect(B.current.data).toEqual({ key: 'value' })
expect(A.current.data).toEqual({ key: 'value' })
expect(B.current.data).toEqual({ key: 'value' })

act(() => {
B.current.update('/key', (value) => {
return 'changed ' + value
act(() => {
B.current.update('/key', (value) => {
return 'changed ' + value
})
})

expect(A.current.data).toEqual({ key: 'changed value' })
expect(B.current.data).toEqual({ key: 'changed value' })
})

expect(A.current.data).toEqual({ key: 'changed value' })
expect(B.current.data).toEqual({ key: 'changed value' })
})
it('should support update without a function', () => {
const props = { key: 'value' }

it('should support update without a function', () => {
const props = { key: 'value' }
const { result: A } = renderHook(() => useData(identifier))
const { result: B } = renderHook(() => useData(identifier, props))

const { result: A } = renderHook(() => useData(identifier))
const { result: B } = renderHook(() => useData(identifier, props))
expect(A.current.data).toEqual({ key: 'value' })
expect(B.current.data).toEqual({ key: 'value' })

expect(A.current.data).toEqual({ key: 'value' })
expect(B.current.data).toEqual({ key: 'value' })
act(() => {
B.current.update('/key', 'new value')
})

act(() => {
B.current.update('/key', 'new value')
expect(A.current.data).toEqual({ key: 'new value' })
expect(B.current.data).toEqual({ key: 'new value' })
})

expect(A.current.data).toEqual({ key: 'new value' })
expect(B.current.data).toEqual({ key: 'new value' })
it('should update data with handler id', () => {
const { result } = renderHook(() => useData(identifier), {
wrapper: ({ children }) => (
<>
<Provider id={identifier}>
<Field.String path="/foo" defaultValue="foo" />
<Field.String path="/bar" defaultValue="bar" />
</Provider>

{children}
</>
),
})

expect(result.current.data).toEqual({
foo: 'foo',
bar: 'bar',
})

act(() => {
result.current.update('/foo', 'updated')
})

expect(result.current.data).toEqual({
foo: 'updated',
bar: 'bar',
})
})
})

it('should rerender when shared state calls "set"', () => {
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { createSharedState } from '../../../../shared/helpers/useSharedState'
import {
SharedStateId,
createReferenceKey,
createSharedState,
} from '../../../../shared/helpers/useSharedState'
import { SharedAttachments } from '../../DataContext/Provider'

export default function clearData(id: string) {
export default function clearData(id: SharedStateId) {
const sharedAttachments = createSharedState<SharedAttachments<unknown>>(
id + '-attachments'
createReferenceKey(id, 'attachments')
)
sharedAttachments.data.clearData?.()
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pointer from '../../utils/json-pointer'
import {
SharedStateId,
createReferenceKey,
createSharedState,
} from '../../../../shared/helpers/useSharedState'
import { SharedAttachments } from '../../DataContext/Provider'
@@ -23,7 +24,7 @@ export default function getData<Data>(
): SetDataReturn<Data> {
const sharedState = createSharedState(id)
const sharedAttachments = createSharedState<SharedAttachments<Data>>(
id + '-attachments'
createReferenceKey(id, 'attachments')
)

const data = sharedState.get() as Data
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import {
import pointer, { JsonObject } from '../../utils/json-pointer'
import {
SharedStateId,
createReferenceKey,
useSharedState,
} from '../../../../shared/helpers/useSharedState'
import useMountEffect from '../../../../shared/helpers/useMountEffect'
@@ -89,7 +90,7 @@ export default function useData<Data = JsonObject>(
)

sharedAttachmentsRef.current = useSharedState<SharedAttachments<Data>>(
id + '-attachments',
createReferenceKey(id, 'attachments'),
{ rerenderUseDataHook: forceUpdate }
)

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useCallback, useContext, useMemo } from 'react'
import {
SharedStateId,
createReferenceKey,
useSharedState,
} from '../../../../shared/helpers/useSharedState'
import DataContext, { ContextState } from '../../DataContext/Context'
@@ -19,7 +20,7 @@ export default function useValidation(
): UseDataReturn {
const { data } = useSharedState<
UseDataReturn & SharedAttachments<unknown>
>(id + '-attachments')
>(createReferenceKey(id, 'attachments'))

const fallback = useCallback(() => false, [])

@@ -62,7 +63,7 @@ type UseConnectionsSharedState = {

function useConnections(id: SharedStateId = undefined) {
const { get } = useSharedState<UseConnectionsSharedState>(
id + '-attachments'
createReferenceKey(id, 'attachments')
)

const dataContext = useContext(DataContext)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useCallback, useContext } from 'react'
import {
SharedStateId,
createReferenceKey,
useSharedState,
} from '../../../shared/helpers/useSharedState'
import DataContext, { ContextState } from '../DataContext/Context'
@@ -10,7 +11,7 @@ export default function useDataContext(id: SharedStateId = undefined): {
getContext: () => ContextState
} {
const sharedDataContext = useSharedState<ContextState>(
id + '-data-context'
createReferenceKey(id, 'data-context')
)

const dataContext = useContext(DataContext)
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { renderHook, act } from '@testing-library/react'
import { makeUniqueId } from '../../component-helper'
import { useSharedState, createSharedState } from '../useSharedState'
import {
useSharedState,
createSharedState,
SharedStateId,
createReferenceKey,
} from '../useSharedState'
import { createContext } from 'react'

describe('useSharedState', () => {
let identifier: string
let identifier: SharedStateId

beforeEach(() => {
identifier = makeUniqueId()
@@ -47,6 +53,50 @@ describe('useSharedState', () => {
expect(result.current.data).toEqual({ test: 'changed' })
})

it('should update the shared state with a function reference as id', () => {
const identifier = () => null
const { result } = renderHook(() =>
useSharedState(identifier, { test: 'initial' })
)
act(() => {
result.current.update({ test: 'updated' })
})
expect(result.current.data).toEqual({ test: 'updated' })
})

it('should update the shared state with an async function reference as id', () => {
const identifier = async () => null
const { result } = renderHook(() =>
useSharedState(identifier, { test: 'initial' })
)
act(() => {
result.current.update({ test: 'updated' })
})
expect(result.current.data).toEqual({ test: 'updated' })
})

it('should update the shared state with an object reference as id', () => {
const identifier = {}
const { result } = renderHook(() =>
useSharedState(identifier, { test: 'initial' })
)
act(() => {
result.current.update({ test: 'updated' })
})
expect(result.current.data).toEqual({ test: 'updated' })
})

it('should update the shared state with a React context reference as id', () => {
const identifier = createContext(null)
const { result } = renderHook(() =>
useSharedState(identifier, { test: 'initial' })
)
act(() => {
result.current.update({ test: 'updated' })
})
expect(result.current.data).toEqual({ test: 'updated' })
})

it('should unsubscribe from the shared state when the component unmounts', () => {
const { result, unmount } = renderHook(() =>
useSharedState(identifier, { test: 'initial' })
@@ -171,3 +221,66 @@ describe('useSharedState', () => {
expect(resultB.current.data).toEqual({ foo: 'baz' })
})
})

describe('createReferenceKey', () => {
it('should return the same object for the same references', () => {
const ref1 = {}
const ref2 = () => null

const key1 = createReferenceKey(ref1, ref2)
const key2 = createReferenceKey(ref1, ref2)

expect(key1).toBe(key2)
})

it('should return the same object for the same string references', () => {
const ref1 = {}
const ref2 = 'unique'

const key1 = createReferenceKey(ref1, ref2)
const key2 = createReferenceKey(ref1, ref2)

expect(key1).toBe(key2)
})

it('should return different objects for different references', () => {
const ref1 = {}
const ref2 = {}
const ref3 = {}

const key1 = createReferenceKey(ref1, ref2)
const key2 = createReferenceKey(ref1, ref3)

expect(key1).not.toBe(key2)
})

it('should return different objects for different first references', () => {
const ref1 = {}
const ref2 = {}
const ref3 = {}

const key1 = createReferenceKey(ref1, ref2)
const key2 = createReferenceKey(ref3, ref2)

expect(key1).not.toBe(key2)
})

it('should cache the combined reference', () => {
const ref1 = {}
const ref2 = () => null

const key1 = createReferenceKey(ref1, ref2)
const key2 = createReferenceKey(ref1, ref2)

expect(key1).toBe(key2)
})

it('should create a new reference if it does not exist', () => {
const ref1 = {}
const ref2 = {}

const key1 = createReferenceKey(ref1, ref2)

expect(key1).toBeDefined()
})
})
71 changes: 48 additions & 23 deletions packages/dnb-eufemia/src/shared/helpers/useSharedState.tsx
Original file line number Diff line number Diff line change
@@ -12,20 +12,22 @@ import useMountEffect from './useMountEffect'
const useLayoutEffect =
typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect

export type SharedStateId = string
export type SharedStateId =
| string
| (() => void)
| Promise<() => void>
| React.Context<any>
| Record<string, unknown>

/**
* Custom hook that provides shared state functionality.
*
* @template Data - The type of data stored in the shared state.
* @param {SharedStateId} id - The identifier for the shared state.
* @param {Data} initialData - The initial data for the shared state.
* @param {Function} onChange - Optional callback function to be called when the shared state is set from another instance/component.
* @returns {Object} - An object containing the shared state data, update function, extend function, and set function.
*/
export function useSharedState<Data>(
id: SharedStateId,
/** The identifier for the shared state. */
id: SharedStateId | undefined,
/** The initial data for the shared state. */
initialData: Data = undefined,
/** Optional callback function to be called when the shared state is set from another instance/component. */
onChange = null
) {
const [, forceUpdate] = useReducer(() => ({}), {})
@@ -53,7 +55,7 @@ export function useSharedState<Data>(
}, [id, initialData])
const sharedAttachment = useMemo(() => {
if (id) {
return createSharedState(id + '-oc', { onChange })
return createSharedState(createReferenceKey(id, 'oc'), { onChange })
}
}, [id, onChange])

@@ -141,26 +143,27 @@ interface SharedStateInstance<Data> extends SharedStateReturn<Data> {
hadInitialData: boolean
}

const sharedStates: Record<SharedStateId, SharedStateInstance<any>> = {}
const sharedStates: Map<
SharedStateId,
SharedStateInstance<any>
> = new Map()

/**
* Creates a shared state instance with the specified ID and initial data.
* @template Data The type of data stored in the shared state.
* @param id The ID of the shared state.
* @param initialData The initial data for the shared state.
* @returns The created shared state instance.
*/
export function createSharedState<Data>(
/** The identifier for the shared state. */
id: SharedStateId,
/** The initial data for the shared state. */
initialData?: Data
): SharedStateInstance<Data> {
if (!sharedStates[id]) {
if (!sharedStates.get(id)) {
let subscribers: Subscriber[] = []

const get = () => sharedStates[id].data
const get = () => sharedStates.get(id).data

const set = (newData: Partial<Data>) => {
sharedStates[id].data = { ...newData }
sharedStates.get(id).data = { ...newData }
}

const update = (newData: Partial<Data>) => {
@@ -169,7 +172,10 @@ export function createSharedState<Data>(
}

const extend = (newData: Data) => {
sharedStates[id].data = { ...sharedStates[id].data, ...newData }
sharedStates.get(id).data = {
...sharedStates.get(id).data,
...newData,
}
sync()
}

@@ -187,7 +193,7 @@ export function createSharedState<Data>(
subscribers.forEach((subscriber) => subscriber())
}

sharedStates[id] = {
sharedStates.set(id, {
data: undefined,
get,
set,
@@ -196,17 +202,36 @@ export function createSharedState<Data>(
subscribe,
unsubscribe,
hadInitialData: Boolean(initialData),
} as SharedStateInstance<Data>
} as SharedStateInstance<Data>)

if (initialData) {
extend(initialData)
}
} else if (
sharedStates[id].data === undefined &&
sharedStates.get(id).data === undefined &&
initialData !== undefined
) {
sharedStates[id].data = { ...initialData }
sharedStates.get(id).data = { ...initialData }
}

return sharedStates.get(id)
}

/**
* Creates a reference key for the shared state.
* You can pass any JavaScript instance as the reference.
*/
export function createReferenceKey(ref1, ref2) {
if (!cache.has(ref1)) {
cache.set(ref1, new Map())
}

const innerMap = cache.get(ref1)

if (!innerMap.has(ref2)) {
innerMap.set(ref2, {})
}

return sharedStates[id]
return innerMap.get(ref2)
}
const cache = new Map()

0 comments on commit a6e3bc3

Please sign in to comment.