diff --git a/apps/react/app/App.tsx b/apps/react/app/App.tsx
index f0a3dfcd..b597e025 100644
--- a/apps/react/app/App.tsx
+++ b/apps/react/app/App.tsx
@@ -11,11 +11,11 @@ import {
Document,
Sheet,
} from '@flatfile/react'
-import React, { useState } from 'react'
+import React, { useEffect, useState } from 'react'
import styles from './page.module.css'
import { recordHook } from '@flatfile/plugin-record-hook'
+
import api from '@flatfile/api'
-import { config } from '../../../packages/cli/src/config'
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
@@ -25,10 +25,8 @@ const App = () => {
const [label, setLabel] = useState('Rock')
useListener((listener) => {
- // currentListener
listener.on('**', (event) => {
console.log('FFApp useListener Event => ', event.topic)
- // Handle the workbook:deleted event
})
}, [])
@@ -41,11 +39,11 @@ const App = () => {
useListener((client) => {
client.use(
- recordHook('contacts', (record) => {
+ recordHook('contacts2', (record) => {
const firstName = record.get('firstName')
console.log({ firstName })
- // Gettign the real types here would be nice but seems tricky
- record.set('email', 'Rock')
+
+ record.set('lastName', 'Rock')
return record
})
)
@@ -60,12 +58,10 @@ const App = () => {
[label]
)
- useEvent('workbook:created', (event) => {
- console.log('workbook:created', { event })
- })
-
- useEvent('*:created', (event) => {
- console.log({ topic: event.topic })
+ useEvent('space:created', async ({ context: { spaceId } }) => {
+ console.log('workbook:created')
+ const { data: space } = await api.spaces.get(spaceId)
+ console.log({ space })
})
useEvent('job:ready', { job: 'sheet:submitActionFg' }, async (event) => {
@@ -102,9 +98,6 @@ const App = () => {
}
})
- const listenerConfig = (label: string) => {
- setLabel(label)
- }
return (
@@ -115,11 +108,12 @@ const App = () => {
>
{open ? 'CLOSE' : 'OPEN'} PORTAL
-
-
+
+
{
>
{
- console.log('onSubmit', { sheet })
+ config={{ ...workbook, name: "ALEX'S WORKBOOK" }}
+ onSubmit={async (sheet) => {
+ console.log('on Workbook Submit ', { sheet })
}}
onRecordHooks={[
[
- '**',
(record) => {
- record.set('email', 'TEST SHEET RECORD')
+ record.set('email', 'SHEET 1 RECORDHOOKS')
+ return record
+ },
+ ],
+ [
+ (record) => {
+ record.set('email', 'SHEET 2 RECORDHOOKS')
return record
},
],
]}
- />
- {/* {
- console.log('onSubmit', { sheet })
- }}
- /> */}
+ >
+ {
+ record.set('email', 'SHEET 3 RECORDHOOK')
+ return record
+ }}
+ onSubmit={async (sheet) => {
+ console.log('onSubmit from Sheet 3', { sheet })
+ }}
+ />
+ {
+ record.set('email', 'SHEET 4 RECORDHOOK')
+ return record
+ }}
+ onSubmit={(sheet) => {
+ console.log('onSubmit from Sheet 4', { sheet })
+ }}
+ />
+
)
diff --git a/apps/react/app/page.tsx b/apps/react/app/page.tsx
index 3c601cdc..c2232dd7 100644
--- a/apps/react/app/page.tsx
+++ b/apps/react/app/page.tsx
@@ -12,6 +12,7 @@ export default function Home() {
publishableKey={PUBLISHABLE_KEY}
config={{
mountElement: 'example-mount-class',
+ preload: true
}}
>
diff --git a/package-lock.json b/package-lock.json
index e1f8ee00..04fa3a17 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -34816,7 +34816,7 @@
"license": "ISC",
"dependencies": {
"@flatfile/api": "^1.7.11",
- "@flatfile/listener": "^1.0.0",
+ "@flatfile/listener": "^1.0.1",
"@flatfile/util-common": "^1.1.0",
"pubnub": "^7.2.2"
},
@@ -34860,26 +34860,6 @@
"resolved": "https://registry.npmjs.org/@flatfile/cross-env-config/-/cross-env-config-0.0.4.tgz",
"integrity": "sha512-mNaqtASTly4N09pjQts5zDnYXFLC891TCxJEiFUnil8p6lQciyd0gnPSnhJD0TTlO5817gX3mLE9RDoAETtIbg=="
},
- "packages/embedded-utils/node_modules/@flatfile/listener": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@flatfile/listener/-/listener-1.0.1.tgz",
- "integrity": "sha512-XClO1KhQDL9Q7zfTANcMeole7Q/2QaIhzttZI+p04PjB+bUlIvk+yUbHvhnLahNkvLHRoIth4p+oBj8tPC1cxA==",
- "dependencies": {
- "ansi-colors": "^4.1.3",
- "cross-fetch": "^4.0.0",
- "flat": "^5.0.2",
- "pako": "^2.1.0",
- "wildcard-match": "^5.1.2"
- },
- "peerDependencies": {
- "@flatfile/api": "^1.5.10"
- }
- },
- "packages/embedded-utils/node_modules/@flatfile/listener/node_modules/pako": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
- "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
- },
"packages/embedded-utils/node_modules/@flatfile/util-common": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@flatfile/util-common/-/util-common-1.1.0.tgz",
diff --git a/packages/embedded-utils/package.json b/packages/embedded-utils/package.json
index 1f892096..506a1d08 100644
--- a/packages/embedded-utils/package.json
+++ b/packages/embedded-utils/package.json
@@ -19,7 +19,7 @@
},
"dependencies": {
"@flatfile/api": "^1.7.11",
- "@flatfile/listener": "^1.0.0",
+ "@flatfile/listener": "^1.0.1",
"@flatfile/util-common": "^1.1.0",
"pubnub": "^7.2.2"
},
diff --git a/packages/embedded-utils/src/types/Space.ts b/packages/embedded-utils/src/types/Space.ts
index c903edea..4ba59342 100644
--- a/packages/embedded-utils/src/types/Space.ts
+++ b/packages/embedded-utils/src/types/Space.ts
@@ -117,7 +117,7 @@ export interface SimpleOnboarding extends NewSpaceFromPublishableKey {
sheet?: any
job?: any
event?: FlatfileEvent
- }) => void
+ }) => Promise | void
onRecordHook?: (
record: FlatfileRecord>,
event?: FlatfileEvent
diff --git a/packages/listener/src/events/event.handler.ts b/packages/listener/src/events/event.handler.ts
index 1a492899..7851c69c 100644
--- a/packages/listener/src/events/event.handler.ts
+++ b/packages/listener/src/events/event.handler.ts
@@ -129,7 +129,7 @@ export class EventHandler extends AuthenticatedClient {
event: FlatfileEvent | Flatfile.Event | any
): Promise {
if (!event) return
- const eventPayload = event.src ? event.src : event
+ const eventPayload = event.src || event
event = new FlatfileEvent(eventPayload, this._accessToken, this._apiUrl)
diff --git a/packages/react/index.html b/packages/react/index.html
deleted file mode 100644
index fc821334..00000000
--- a/packages/react/index.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
- Flatfile X
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/packages/react/src/components/Document.tsx b/packages/react/src/components/Document.tsx
index 0ab5d0ab..96fb6176 100644
--- a/packages/react/src/components/Document.tsx
+++ b/packages/react/src/components/Document.tsx
@@ -4,7 +4,26 @@ import { useCallback, useContext } from 'react'
import type { Flatfile } from '@flatfile/api'
import { useDeepCompareEffect } from '../utils/useDeepCompareEffect'
-export const Document = (props: { config: Flatfile.DocumentConfig }) => {
+type DocumentProps = {
+ config: Flatfile.DocumentConfig
+}
+/**
+ * `Document` component responsible for updating the document configuration within the Flatfile context.
+ * It utilizes the `useDeepCompareEffect` hook to deeply compare the `config` prop changes and update the document accordingly.
+ *
+ * @component
+ * @example
+ * const documentConfig = {
+ * title: "Document Title",
+ * body: "Example Body",
+ * }
+ * return
+ *
+ * @param {DocumentProps} props - The props for the Document component.
+ * @param {Flatfile.DocumentConfig} props.config - The configuration object for the document.
+ */
+
+export const Document = (props: DocumentProps) => {
const { config } = props
const { updateDocument } = useContext(FlatfileContext)
diff --git a/packages/react/src/components/FlatfileContext.tsx b/packages/react/src/components/FlatfileContext.tsx
index 00c76a88..457116fc 100644
--- a/packages/react/src/components/FlatfileContext.tsx
+++ b/packages/react/src/components/FlatfileContext.tsx
@@ -8,6 +8,23 @@ type ReUseSpace = Partial & {
id: string
accessToken: string
}
+
+export const DEFAULT_CREATE_SPACE = {
+ document: undefined,
+ workbook: {
+ name: 'Embedded Workbook',
+ sheets: [],
+ },
+ space: {
+ name: 'Embedded Space',
+ labels: ['embedded'],
+ namespace: 'portal',
+ metadata: {
+ sidebarConfig: { showSidebar: false },
+ },
+ },
+}
+
export interface FlatfileContextType {
publishableKey?: string
environmentId?: string
diff --git a/packages/react/src/components/FlatfileProvider.tsx b/packages/react/src/components/FlatfileProvider.tsx
index cd77823c..6198aeca 100644
--- a/packages/react/src/components/FlatfileProvider.tsx
+++ b/packages/react/src/components/FlatfileProvider.tsx
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'
-import FlatfileContext from './FlatfileContext'
+import FlatfileContext, { DEFAULT_CREATE_SPACE } from './FlatfileContext'
import FlatfileListener, { Browser } from '@flatfile/listener'
import { Flatfile } from '@flatfile/api'
import { EmbeddedIFrameWrapper } from './EmbeddedIFrameWrapper'
@@ -24,32 +24,18 @@ export const FlatfileProvider: React.FC = ({
document: Flatfile.DocumentConfig | undefined
workbook: Flatfile.CreateWorkbookConfig
space: Flatfile.SpaceConfig
- }>({
- document: undefined,
- workbook: {
- name: 'Embedded Workbook',
- sheets: [],
- },
- space: {
- name: 'Embedded Space',
- labels: ['embedded'],
- namespace: 'portal',
- metadata: {
- sidebarConfig: { showSidebar: false },
- },
- },
- })
+ }>(DEFAULT_CREATE_SPACE)
const addSheet = (newSheet: Flatfile.SheetConfig) => {
setCreateSpace((prevSpace) => {
// Check if the sheet already exists
const sheetExists = prevSpace.workbook.sheets?.some(
- (sheet: any) => sheet.slug === newSheet.slug
+ (sheet) => sheet.slug === newSheet.slug
)
if (sheetExists) {
- return prevSpace // Return the state unchanged if the sheet exists
+ return prevSpace
}
- // Add the new sheet if it doesn't exist
+
return {
...prevSpace,
workbook: {
@@ -88,6 +74,15 @@ export const FlatfileProvider: React.FC = ({
workbook: {
...prevSpace.workbook,
...workbookUpdates,
+ // Prioritize order of sheets passed along in the Workbook.config then subsequent components
+ actions: [
+ ...(workbookUpdates.actions || []),
+ ...(prevSpace.workbook.actions || []),
+ ],
+ sheets: [
+ ...(workbookUpdates.sheets || []),
+ ...(prevSpace.workbook.sheets || []),
+ ],
},
}))
}
@@ -118,6 +113,7 @@ export const FlatfileProvider: React.FC = ({
listener.dispatchEvent(flatfileEvent)
}
+ // Listen to the postMessage event from the created iFrame
useEffect(() => {
window.addEventListener('message', handlePostMessage, false)
return () => {
@@ -125,6 +121,7 @@ export const FlatfileProvider: React.FC = ({
}
}, [listener])
+ // Mount the event listener to the FlatfileProvider
useEffect(() => {
if (listener && internalAccessToken) {
listener.mount(
diff --git a/packages/react/src/components/Sheet.tsx b/packages/react/src/components/Sheet.tsx
index 7d6c4568..0f12045e 100644
--- a/packages/react/src/components/Sheet.tsx
+++ b/packages/react/src/components/Sheet.tsx
@@ -14,62 +14,94 @@ import { usePlugin, useEvent } from '../hooks'
import { useDeepCompareEffect } from '../utils/useDeepCompareEffect'
import { workbookOnSubmitAction } from '../utils/constants'
-export const Sheet = (
- props: { config: Flatfile.SheetConfig } & Pick<
- SimpleOnboarding,
- 'onRecordHook' | 'onSubmit' | 'submitSettings'
- >
-) => {
- const { config, onRecordHook, onSubmit, ...sheetProps } = props
+type SheetProps = {
+ config: Flatfile.SheetConfig
+ onSubmit?: SimpleOnboarding['onSubmit']
+ submitSettings?: SimpleOnboarding['submitSettings']
+ onRecordHook?: SimpleOnboarding['onRecordHook']
+}
+/**
+ * `Sheet` component for Flatfile integration.
+ *
+ * This component allows you to integrate Flatfile's data import capabilities into your React application.
+ * It provides a way to configure and handle the import process, including submitting data and handling records.
+ *
+ * @component
+ * @example
+ * const config = {
+ * name: 'Contacts',
+ * slug: 'contacts',
+ * fields: [
+ * { label: 'First Name', key: 'firstName', type: 'string', },
+ * { label: 'Last Name', key: 'lastName', type: 'string', },
+ * { label: 'Email', key: 'email', type: 'string', contraints: [{ type: 'unique' }] }
+ * ]
+ * }
+ *
+ * const onSubmit = async (results) => {
+ * console.log('Data submitted:', results.data)
+ * }
+ *
+ * const onRecordHook = async (record, event) => {
+ * if (!record.email.includes('@example.com')) {
+ * return {
+ * email: {
+ * value: record.email,
+ * info: 'Only @example.com emails are allowed',
+ * level: 'error'
+ * }
+ * }
+ * }
+ * }
+ *
+ *
+ *
+ * @param {Object} props - Component props
+ * @param {Flatfile.SheetConfig} props.config - Configuration for the Flatfile import
+ * @param {Function} [props.onSubmit] - Callback function to handle data submission
+ * @param {Object} [props.submitSettings] - Settings for data submission
+ * @param {Function} [props.onRecordHook] - Callback function to handle record manipulation
+ */
+
+export const Sheet = (props: SheetProps) => {
+ const { config, onRecordHook, onSubmit, submitSettings } = props
const { addSheet, updateWorkbook, createSpace } = useContext(FlatfileContext)
const callback = useCallback(() => {
- let tmp
- // Adds an onSubmit action to the workbook if an onSubmit function is provided
+ // Manage actions immutably
if (onSubmit) {
- if (!createSpace?.workbook.actions) {
- tmp = {
- actions: [workbookOnSubmitAction],
- }
- } else {
- createSpace.workbook.actions = [
- workbookOnSubmitAction,
- ...(config.actions || []),
- ]
- }
+ updateWorkbook({
+ actions: [
+ workbookOnSubmitAction(config.slug),
+ ...(createSpace.workbook.actions || []),
+ ],
+ })
}
- updateWorkbook(tmp ?? createSpace.workbook)
addSheet(config)
- }, [config, addSheet, updateWorkbook])
+ }, [config, createSpace, addSheet, updateWorkbook, onSubmit])
useDeepCompareEffect(callback, [config])
if (onRecordHook) {
- if (!config) {
- throw new Error(
- 'You must provide a sheet configuration to use the onRecordHook'
- )
- }
usePlugin(
recordHook(
config.slug || '**',
async (record: FlatfileRecord, event: FlatfileEvent | undefined) => {
- // @ts-ignore - something weird with the `data` prop and the types upstream in the packages being declared in different places, but overall this is fine
return onRecordHook(record, event)
}
),
- []
+ [config, onRecordHook]
)
}
if (onSubmit) {
const onSubmitSettings = {
...DefaultSubmitSettings,
- ...props.submitSettings,
+ ...submitSettings,
}
useEvent(
'job:ready',
- { job: 'workbook:simpleSubmitAction' },
+ { job: `workbook:${workbookOnSubmitAction(config.slug).operation}` },
async (event) => {
const { jobId, spaceId, workbookId } = event.context
const FlatfileAPI = new FlatfileClient()
@@ -84,8 +116,14 @@ export const Sheet = (
workbookId,
})
- // this assumes we are only allowing 1 sheet here (which we've talked about doing initially)
- const sheet = new SheetHandler(workbookSheets[0].id)
+ const thisSheet = workbookSheets.find((s) => s.slug === config.slug)
+
+ if (!thisSheet) {
+ throw new Error(
+ `Failed to find sheet slug:${config.slug} in the workbook id: ${workbookId}`
+ )
+ }
+ const sheet = new SheetHandler(thisSheet.id)
if (onSubmit) {
await onSubmit({ job, sheet, event })
diff --git a/packages/react/src/components/Space.tsx b/packages/react/src/components/Space.tsx
index 2e351d99..747cfeef 100644
--- a/packages/react/src/components/Space.tsx
+++ b/packages/react/src/components/Space.tsx
@@ -1,20 +1,40 @@
-import FlatfileContext from './FlatfileContext'
import React, { useCallback, useContext } from 'react'
import type { Flatfile } from '@flatfile/api'
+import FlatfileContext from './FlatfileContext'
import { useDeepCompareEffect } from '../utils/useDeepCompareEffect'
+type SpaceProps = {
+ id?: string
+ config: Flatfile.SpaceConfig
+ children?: React.ReactNode
+}
+
/**
- * @name Space
- * @description Flatfile Embedded SpaceWrapper component
- * @param props
+ * `Space` component for integrating Flatfile's space functionality within a React application.
+ * This component allows for the configuration of a Flatfile space and renders its children within the context of that space.
+ *
+ * @component
+ * @example
+ * const spaceConfig = {
+ * name: 'Example Space',
+ * metadata: {},
+ * }
+ *
+ *
+ *
+ *
+ *
+ *
+ * @param {SpaceProps} props - The properties passed to the Space component.
+ * @param {string} [props.id] - Optional ID for the space component.
+ * @param {Flatfile.SpaceConfig} props.config - Configuration object for the Flatfile space.
+ * @param {React.ReactNode} [props.children] - Child components to be rendered within the Space component.
+ * @returns {React.ReactElement} A React component that renders the Flatfile space.
*/
-export const Space = (props: {
- config: Flatfile.SpaceConfig & { id?: string }
- children?: React.ReactNode
-}) => {
+export const Space = (props: SpaceProps) => {
const { config, children } = props
- const { updateSpace, createSpace } = useContext(FlatfileContext)
+ const { updateSpace } = useContext(FlatfileContext)
const callback = useCallback(() => {
updateSpace(config)
diff --git a/packages/react/src/components/Workbook.tsx b/packages/react/src/components/Workbook.tsx
index 2ca6f938..f268351b 100644
--- a/packages/react/src/components/Workbook.tsx
+++ b/packages/react/src/components/Workbook.tsx
@@ -1,9 +1,9 @@
import FlatfileContext from './FlatfileContext'
-import React, { useCallback, useContext } from 'react'
+import React, { useCallback, useContext, useEffect, useRef } from 'react'
import { FlatfileClient, type Flatfile } from '@flatfile/api'
import { useDeepCompareEffect } from '../utils/useDeepCompareEffect'
import { TRecordDataWithLinks, TPrimitive } from '@flatfile/hooks'
-import { FlatfileEvent } from '@flatfile/listener'
+import FlatfileListener, { FlatfileEvent } from '@flatfile/listener'
import { FlatfileRecord, recordHook } from '@flatfile/plugin-record-hook'
import { useEvent, usePlugin } from '../hooks'
import {
@@ -14,74 +14,106 @@ import {
} from '@flatfile/embedded-utils'
import { workbookOnSubmitAction } from '../utils/constants'
-type onRecordHook = (record: T, event?: FlatfileEvent) => FlatfileRecord
+export type onRecordHook = (
+ record: T,
+ event?: FlatfileEvent
+) => FlatfileRecord
type HookConfig = [string, onRecordHook] | [onRecordHook]
-type onRecordHooks = HookConfig[]
+export type onRecordHooks = HookConfig[]
-export const Workbook = (
- props: {
- config?: Flatfile.CreateWorkbookConfig
- children?: React.ReactNode
- } & Pick & {
- onRecordHooks?: onRecordHooks<
- FlatfileRecord>
- >
- }
-) => {
+type WorkbookProps = Partial<{
+ config: Flatfile.CreateWorkbookConfig
+ onSubmit: SimpleOnboarding['onSubmit']
+ submitSettings: SimpleOnboarding['submitSettings']
+ onRecordHooks: onRecordHooks>>
+ children: React.ReactNode
+}>
+
+/**
+ * `Workbook` component for integrating Flatfile's import functionality within a React application.
+ * This component allows for the configuration of a Flatfile workbook, submission settings, and record hooks.
+ *
+ * @component
+ * @example
+ * const config = {
+ * name: 'Example Workbook',
+ * sheets: []
+ * }
+ * const onSubmit = (data) => console.log(data)
+ * const submitSettings = {...}
+ * const onRecordHooks = [
+ * ['slug', (record) => {
+ * record.set('key', 'foo')
+ * return record
+ * }]
+ * ]
+ *
+ *
+ *
+ *
+ *
+ * @param {WorkbookProps} props - The properties passed to the Workbook component.
+ * @param {Flatfile.CreateWorkbookConfig} [props.config] - Configuration object for the Flatfile workbook.
+ * @param {Function} [props.onSubmit] - Callback function to be executed upon submission of the workbook.
+ * @param {Object} [props.submitSettings] - Settings object for workbook submission.
+ * @param {onRecordHooks>>} [props.onRecordHooks] - Array of hooks to be executed on each record.
+ * @param {React.ReactNode} [props.children] - Child components to be rendered within the Workbook component.
+ * @returns {React.ReactElement} A React component that renders the Flatfile workbook.
+ */
+
+export const Workbook = (props: WorkbookProps) => {
const { config, children, onRecordHooks, onSubmit } = props
const { updateWorkbook, createSpace } = useContext(FlatfileContext)
- const { sheets } = createSpace.workbook
// Accept a workbook onSubmit function and add it to the workbook actions
const callback = useCallback(() => {
- let tmp
- if (onSubmit) {
- if (!config?.actions) {
- tmp = {
- actions: [workbookOnSubmitAction],
- }
- } else {
- config.actions = [workbookOnSubmitAction, ...(config.actions || [])]
- }
- }
- updateWorkbook(tmp ?? config)
- }, [config, updateWorkbook])
+ // adds workbook action if onSubmit is passed along
+ updateWorkbook(
+ onSubmit
+ ? {
+ ...config,
+ actions: [workbookOnSubmitAction(), ...(config?.actions || [])],
+ }
+ : config
+ )
+ }, [config, onSubmit])
useDeepCompareEffect(callback, [config])
- if (onRecordHooks) {
- onRecordHooks.forEach(([slug, hook], index) => {
- if (typeof slug === 'function') {
- usePlugin(
- recordHook(
- sheets?.[index].slug || '**',
- async (
- record: FlatfileRecord,
- event: FlatfileEvent | undefined
- ) => {
+ usePlugin(
+ (client) => {
+ onRecordHooks?.map(([slug, hook], index) => {
+ // If you have multiple sheets, and just pass 1 record hook to the onRecordHooks array and that record hook doesn't have a slug, then assume the record hook is for all sheets.
+ // Otherwise if multiple record hooks are passed along with out slugs, then assume they are in the same order as the sheets provided
+ const actualSlug =
+ typeof slug === 'function'
+ ? onRecordHooks?.length === 1 &&
+ createSpace.workbook.sheets?.length > 1
+ ? '**'
+ : createSpace.workbook.sheets?.[index]?.slug
+ : slug
+
+ client.use(
+ recordHook(actualSlug, async (record, event) => {
+ if (typeof slug === 'function') {
return slug(record, event)
- }
- ),
- []
- )
- } else if (typeof slug === 'string' && typeof hook === 'function') {
- usePlugin(
- recordHook(
- slug,
- async (
- record: FlatfileRecord,
- event: FlatfileEvent | undefined
- ) => {
+ } else if (typeof hook === 'function') {
+ // Ensure hook is a function before invoking
return hook(record, event)
}
- ),
- []
+ })
)
- }
- })
- }
+ })
+ },
+ [config, createSpace.workbook.sheets, onRecordHooks]
+ )
if (onSubmit) {
const onSubmitSettings = {
@@ -90,7 +122,7 @@ export const Workbook = (
}
useEvent(
'job:ready',
- { job: 'workbook:simpleSubmitAction' },
+ { job: `workbook:${workbookOnSubmitAction().operation}` },
async (event) => {
const { jobId, spaceId, workbookId } = event.context
const FlatfileAPI = new FlatfileClient()
diff --git a/packages/react/src/components/_tests_/Document.spec.tsx b/packages/react/src/components/_tests_/Document.spec.tsx
new file mode 100644
index 00000000..8e259612
--- /dev/null
+++ b/packages/react/src/components/_tests_/Document.spec.tsx
@@ -0,0 +1,71 @@
+import React from 'react'
+import { render } from '@testing-library/react'
+import { Document } from '../Document'
+import FlatfileContext from '../FlatfileContext'
+import { useDeepCompareEffect } from '../../utils/useDeepCompareEffect'
+import { MockFlatfileProviderValue } from './FlatfileProvider.spec'
+
+jest.mock('../../utils/useDeepCompareEffect', () => ({
+ useDeepCompareEffect: jest.fn(),
+}))
+
+const MockDocumentConfig = { title: 'Test Document', body: 'Document Content ' }
+
+describe('Document', () => {
+ const mockUpdateDocument = jest.fn()
+
+ // This sets up the context value that will be used in all tests
+ beforeEach(() => {
+ jest.mocked(useDeepCompareEffect).mockImplementation((callback, deps) => {
+ React.useEffect(callback, deps)
+ })
+ jest.clearAllMocks()
+ })
+
+ it('calls updateDocument with config on initial render', () => {
+ // Provide the mock context to the component
+ render(
+
+
+
+ )
+
+ // Check if updateDocument was called with the correct config
+ expect(mockUpdateDocument).toHaveBeenCalledWith(MockDocumentConfig)
+ })
+
+ it('calls updateDocument with new config when config changes', () => {
+ const newConfig = { ...MockDocumentConfig, title: 'newConfig' }
+ const { rerender } = render(
+
+
+
+ )
+
+ // Rerender with new props
+ rerender(
+
+
+
+ )
+
+ // The useEffect should run the callback again with the new config
+ expect(mockUpdateDocument).toHaveBeenCalledTimes(2)
+ expect(mockUpdateDocument).toHaveBeenCalledWith(newConfig)
+ })
+})
diff --git a/packages/react/src/components/_tests_/FlatfileProvider.spec.tsx b/packages/react/src/components/_tests_/FlatfileProvider.spec.tsx
index b124ecb0..cfc9be4b 100644
--- a/packages/react/src/components/_tests_/FlatfileProvider.spec.tsx
+++ b/packages/react/src/components/_tests_/FlatfileProvider.spec.tsx
@@ -1,8 +1,26 @@
import React, { useContext } from 'react'
import { render, waitFor } from '@testing-library/react'
import { FlatfileProvider } from '../FlatfileProvider'
-import FlatfileContext from '../FlatfileContext'
+import FlatfileContext, { FlatfileContextType } from '../FlatfileContext'
import fetchMock from 'jest-fetch-mock'
+import FlatfileListener from '@flatfile/listener'
+
+export const MockFlatfileProviderValue: FlatfileContextType = {
+ updateDocument: jest.fn(),
+ apiUrl: '',
+ open: false,
+ setOpen: jest.fn(),
+ setSessionSpace: jest.fn(),
+ listener: new FlatfileListener(),
+ setListener: jest.fn(),
+ setAccessToken: jest.fn(),
+ addSheet: jest.fn(),
+ updateSheet: jest.fn(),
+ updateWorkbook: jest.fn(),
+ createSpace: undefined,
+ setCreateSpace: jest.fn(),
+ updateSpace: jest.fn(),
+}
fetchMock.enableMocks()
console.error = jest.fn()
diff --git a/packages/react/src/components/_tests_/Sheet.spec.tsx b/packages/react/src/components/_tests_/Sheet.spec.tsx
new file mode 100644
index 00000000..a86a6954
--- /dev/null
+++ b/packages/react/src/components/_tests_/Sheet.spec.tsx
@@ -0,0 +1,102 @@
+import React from 'react'
+import { render } from '@testing-library/react'
+import { Sheet } from '../Sheet'
+import FlatfileContext, {
+ DEFAULT_CREATE_SPACE,
+ FlatfileContextType,
+} from '../FlatfileContext'
+import { useDeepCompareEffect } from '../../utils/useDeepCompareEffect'
+import { workbookOnSubmitAction } from '../../utils/constants'
+import FlatfileListener from '@flatfile/listener'
+import { Flatfile } from '@flatfile/api'
+
+const MockFlatfileProviderValue: FlatfileContextType = {
+ updateDocument: jest.fn(),
+ apiUrl: '',
+ open: false,
+ setOpen: jest.fn(),
+ setSessionSpace: jest.fn(),
+ listener: new FlatfileListener(),
+ setListener: jest.fn(),
+ setAccessToken: jest.fn(),
+ addSheet: jest.fn(),
+ updateSheet: jest.fn(),
+ updateWorkbook: jest.fn(),
+ createSpace: DEFAULT_CREATE_SPACE,
+ setCreateSpace: jest.fn(),
+ updateSpace: jest.fn(),
+}
+
+jest.mock('../../utils/useDeepCompareEffect', () => ({
+ useDeepCompareEffect: jest.fn(),
+}))
+
+const mockUpdateWorkbook = jest.fn()
+const mockUpdateSheet = jest.fn()
+
+const mockConfig: Flatfile.SheetConfig = {
+ name: 'Test Sheet',
+ slug: 'test-sheet',
+ fields: [
+ {
+ label: 'First Name',
+ key: 'firstName',
+ type: 'string',
+ },
+ {
+ label: 'Email',
+ key: 'email',
+ type: 'string',
+ },
+ ],
+}
+
+describe('Sheet', () => {
+ beforeEach(() => {
+ jest.mocked(useDeepCompareEffect).mockImplementation((callback, deps) => {
+ React.useEffect(callback, deps)
+ })
+ jest.clearAllMocks()
+ })
+
+ it('calls updateSheet with config on initial render', () => {
+ render(
+
+
+
+ )
+
+ expect(mockUpdateSheet).toHaveBeenCalledWith(mockConfig)
+ })
+
+ it('calls updateSheet && updateWorkbook with updated config when onSubmit is provided', () => {
+ const onSubmitMock = jest.fn()
+ const updatedConfig = {
+ ...mockConfig,
+ actions: [workbookOnSubmitAction(mockConfig.slug)],
+ }
+
+ render(
+
+
+
+ )
+
+ expect(mockUpdateSheet).toHaveBeenCalledWith(mockConfig)
+ expect(mockUpdateWorkbook).toHaveBeenCalledWith({
+ actions: [workbookOnSubmitAction(mockConfig.slug)],
+ })
+ })
+ // More tests to check interaction during onSubmit and other complex logic
+})
diff --git a/packages/react/src/components/_tests_/Space.spec.tsx b/packages/react/src/components/_tests_/Space.spec.tsx
index 726df3e1..d83f8741 100644
--- a/packages/react/src/components/_tests_/Space.spec.tsx
+++ b/packages/react/src/components/_tests_/Space.spec.tsx
@@ -1,100 +1,58 @@
import React from 'react'
+import { render } from '@testing-library/react'
import { Space } from '../Space'
-import { FlatfileContext, FlatfileContextType } from '../FlatfileContext'
-import { SheetConfig } from '@flatfile/api/api'
-import FlatfileListener from '@flatfile/listener'
-import { Root, createRoot } from 'react-dom/client'
-import { act } from 'react-dom/test-utils'
-
-import '@testing-library/jest-dom'
-import { waitFor } from '@testing-library/react'
-
-const mockContextValue: FlatfileContextType = {
- updateSpace: jest.fn(),
- createSpace: jest.fn(),
- apiUrl: 'https://example.com',
- open: true,
- setOpen: jest.fn(),
- setSessionSpace: jest.fn(),
- listener: new FlatfileListener(),
- setListener: function (listener: FlatfileListener): void {
- throw new Error('Function not implemented.')
- },
- setAccessToken: function (accessToken: string): void {
- throw new Error('Function not implemented.')
- },
- addSheet: function (config: any): void {
- throw new Error('Function not implemented.')
- },
- updateSheet: function (
- sheetSlug: string,
- sheetUpdates: Partial
- ): void {
- throw new Error('Function not implemented.')
- },
- updateWorkbook: function (config: any): void {
- throw new Error('Function not implemented.')
- },
- updateDocument: function (config: any): void {
- throw new Error('Function not implemented.')
- },
- setCreateSpace: function (config: any): void {
- throw new Error('Function not implemented.')
- },
+import FlatfileContext from '../FlatfileContext'
+import { useDeepCompareEffect } from '../../utils/useDeepCompareEffect'
+import { MockFlatfileProviderValue } from './FlatfileProvider.spec'
+jest.mock('../../utils/useDeepCompareEffect', () => ({
+ useDeepCompareEffect: jest.fn(),
+}))
+
+const MockSpaceConfig = {
+ name: 'Test Space',
}
describe('Space', () => {
- let root: Root
- let container: Element | DocumentFragment
+ const mockUpdateSpace = jest.fn()
beforeEach(() => {
- container = document.createElement('div')
- document.body.appendChild(container)
- root = createRoot(container)
- })
-
- afterEach(() => {
- document.body.removeChild(container)
+ jest.mocked(useDeepCompareEffect).mockImplementation((callback, deps) => {
+ React.useEffect(callback, deps)
+ })
+ jest.clearAllMocks()
})
- it('renders children when provided', async () => {
- const mockConfig = {
- id: 'spaceId',
- // Add other necessary properties to mockConfig
- }
- act(() => {
- root.render(
-
-
- Child Element
-
-
- )
- })
+ it('calls updateSpace with config on initial render', () => {
+ render(
+
+
+
+ )
- await waitFor(() => {
- const childElement = document.getElementById('child-element')
- expect(childElement).toBeInTheDocument()
- })
+ expect(mockUpdateSpace).toHaveBeenCalledWith(MockSpaceConfig)
})
- it('does not render children when not provided', async () => {
- const mockConfig = {
- id: 'spaceId',
- // Add other necessary properties to mockConfig
- }
- act(() => {
- root.render(
-
-
-
- )
- })
- await waitFor(() => {
- const childElement = document.getElementById('child-element')
- expect(childElement).not.toBeInTheDocument()
- })
+ it('calls updateSpace with new config when config changes', () => {
+ const newConfig = { ...MockSpaceConfig, name: 'New Test Area' }
+ const { rerender } = render(
+
+
+
+ )
+
+ rerender(
+
+
+
+ )
+
+ expect(mockUpdateSpace).toHaveBeenCalledTimes(2)
+ expect(mockUpdateSpace).toHaveBeenCalledWith(newConfig)
})
-
- // Add more test cases as needed
})
diff --git a/packages/react/src/components/_tests_/Workbook.spec.tsx b/packages/react/src/components/_tests_/Workbook.spec.tsx
new file mode 100644
index 00000000..51424993
--- /dev/null
+++ b/packages/react/src/components/_tests_/Workbook.spec.tsx
@@ -0,0 +1,117 @@
+import React from 'react'
+import { render } from '@testing-library/react'
+import { Workbook } from '../Workbook'
+import FlatfileContext, { FlatfileContextType } from '../FlatfileContext'
+import { useDeepCompareEffect } from '../../utils/useDeepCompareEffect'
+import { workbookOnSubmitAction } from '../../utils/constants'
+import FlatfileListener from '@flatfile/listener'
+import { Flatfile } from '@flatfile/api'
+
+const MockFlatfileProviderValue: FlatfileContextType = {
+ updateDocument: jest.fn(),
+ apiUrl: '',
+ open: false,
+ setOpen: jest.fn(),
+ setSessionSpace: jest.fn(),
+ listener: new FlatfileListener(),
+ setListener: jest.fn(),
+ setAccessToken: jest.fn(),
+ addSheet: jest.fn(),
+ updateSheet: jest.fn(),
+ updateWorkbook: jest.fn(),
+ createSpace: {
+ document: undefined,
+ workbook: {
+ name: 'Embedded Workbook',
+ sheets: [],
+ },
+ space: {
+ name: 'Embedded Space',
+ labels: ['embedded'],
+ namespace: 'portal',
+ metadata: {
+ sidebarConfig: { showSidebar: false },
+ },
+ },
+ },
+ setCreateSpace: jest.fn(),
+ updateSpace: jest.fn(),
+}
+
+jest.mock('../../utils/useDeepCompareEffect', () => ({
+ useDeepCompareEffect: jest.fn(),
+}))
+
+const mockUpdateWorkbook = jest.fn()
+const mockCreateSpace = {
+ workbook: {
+ sheets: [{ slug: 'test-sheet' }],
+ },
+}
+
+const mockConfig: Flatfile.CreateWorkbookConfig = {
+ name: 'Test Workbook',
+ sheets: [
+ {
+ name: 'Test Sheet',
+ slug: 'test-sheet',
+ fields: [
+ {
+ label: 'First Name',
+ key: 'firstName',
+ type: 'string',
+ },
+ {
+ label: 'Email',
+ key: 'email',
+ type: 'string',
+ },
+ ],
+ },
+ ],
+}
+
+describe('Workbook', () => {
+ beforeEach(() => {
+ jest.mocked(useDeepCompareEffect).mockImplementation((callback, deps) => {
+ React.useEffect(callback, deps)
+ })
+ jest.clearAllMocks()
+ })
+
+ it('calls updateWorkbook with config on initial render', () => {
+ render(
+
+
+
+ )
+
+ expect(mockUpdateWorkbook).toHaveBeenCalledWith(mockConfig)
+ })
+
+ it('calls updateWorkbook with updated config when onSubmit is provided', () => {
+ const onSubmitMock = jest.fn()
+ const updatedConfig = {
+ ...mockConfig,
+ actions: [workbookOnSubmitAction()],
+ }
+
+ render(
+
+
+
+ )
+
+ expect(mockUpdateWorkbook).toHaveBeenCalledWith(updatedConfig)
+ })
+})
diff --git a/packages/react/src/components/Error.tsx b/packages/react/src/components/legacy/Error.tsx
similarity index 94%
rename from packages/react/src/components/Error.tsx
rename to packages/react/src/components/legacy/Error.tsx
index d97278b9..1dfc8515 100644
--- a/packages/react/src/components/Error.tsx
+++ b/packages/react/src/components/legacy/Error.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import './style.scss'
+import '../style.scss'
const DefaultError = ({ error }: { error: string | Error }) => {
const errorMessage = typeof error === 'string' ? error : error.message
diff --git a/packages/react/src/components/legacy/InitSpace.tsx b/packages/react/src/components/legacy/InitSpace.tsx
index f6707d9c..3df21084 100644
--- a/packages/react/src/components/legacy/InitSpace.tsx
+++ b/packages/react/src/components/legacy/InitSpace.tsx
@@ -7,7 +7,7 @@ import { authenticate } from '../../utils/authenticate'
import { getSpace } from '../../utils/getSpace'
import { initializeSpace } from '../../utils/initializeSpace'
import ConfirmModal from '../ConfirmCloseModal'
-import DefaultError from '../Error'
+import DefaultError from './Error'
import { getContainerStyles, getIframeStyles } from '../embeddedStyles'
import '../style.scss'
diff --git a/packages/react/src/hooks/_tests_/useEvent.spec.tsx b/packages/react/src/hooks/_tests_/useEvent.spec.tsx
new file mode 100644
index 00000000..a7ee6154
--- /dev/null
+++ b/packages/react/src/hooks/_tests_/useEvent.spec.tsx
@@ -0,0 +1,108 @@
+import React from 'react'
+import { render, waitFor } from '@testing-library/react'
+import { useDeepCompareEffect } from '../../utils/useDeepCompareEffect'
+import { useEvent } from '../../hooks'
+
+import { Flatfile } from '@flatfile/api'
+import { FlatfileProvider } from '../../components/FlatfileProvider'
+import { Workbook } from '../../components'
+
+jest.mock('../../utils/useDeepCompareEffect', () => ({
+ useDeepCompareEffect: jest.fn(),
+}))
+
+const mockConfig: Flatfile.CreateWorkbookConfig = {
+ name: 'Test Workbook',
+ sheets: [
+ {
+ name: 'Test Sheet',
+ slug: 'test-sheet',
+ fields: [
+ {
+ label: 'First Name',
+ key: 'firstName',
+ type: 'string',
+ },
+ {
+ label: 'Email',
+ key: 'email',
+ type: 'string',
+ },
+ ],
+ },
+ ],
+}
+
+const APP = ({
+ useEventFunction,
+}: {
+ useEventFunction: (event: {}) => void
+}) => {
+ useEvent('crazy:event', (event) => {
+ useEventFunction(event)
+ })
+
+ useEvent('*:event', (event) => {
+ useEventFunction(event)
+ })
+
+ useEvent('crazy:*', (event) => {
+ useEventFunction(event)
+ })
+ useEvent('not-crazy:event', (event) => {
+ useEventFunction(event)
+ })
+
+ return (
+ {
+ record.set('email', 'TEST SHEET RECORD')
+ return record
+ },
+ ],
+ ]}
+ onSubmit={async (sheet) => {
+ console.log('onSubmit', { sheet })
+ }}
+ />
+ )
+}
+
+describe('useEvent', () => {
+ beforeEach(() => {
+ jest.mocked(useDeepCompareEffect).mockImplementation((callback, deps) => {
+ React.useEffect(callback, deps)
+ })
+ jest.clearAllMocks()
+ })
+
+ it('registers plugins and handles events when provided', async () => {
+ const useEventMock = jest.fn()
+
+ render(
+
+
+
+ )
+
+ window.postMessage(
+ {
+ flatfileEvent: { topic: 'crazy:event', payload: { alex: 'rock' } },
+ },
+ '*'
+ )
+
+ await waitFor(() => {
+ expect(useEventMock).toHaveBeenCalledWith(
+ expect.objectContaining({ payload: { alex: 'rock' } })
+ )
+ expect(useEventMock).toHaveBeenCalledTimes(3)
+ })
+ })
+
+ // More tests to check interaction during onSubmit and other complex logic
+})
diff --git a/packages/react/src/hooks/legacy/_tests_/useSpace.spec.tsx b/packages/react/src/hooks/legacy/_tests_/useSpace.spec.tsx
index 86749b34..511ff5ab 100644
--- a/packages/react/src/hooks/legacy/_tests_/useSpace.spec.tsx
+++ b/packages/react/src/hooks/legacy/_tests_/useSpace.spec.tsx
@@ -8,7 +8,7 @@ import { FlatfileClient } from '@flatfile/api'
import { renderHook } from '@testing-library/react-hooks'
import { ISpace } from '@flatfile/embedded-utils'
import { waitFor } from '@testing-library/react'
-import DefaultError from '../../../components/Error'
+import DefaultError from '../../../components/legacy/Error'
import { mockDocument, mockSpace, mockWorkbook } from '../../../test/mocks'
import useSpace from '../useSpace'
import Space from '../../../components/legacy/LegacySpace'
diff --git a/packages/react/src/hooks/legacy/usePortal.tsx b/packages/react/src/hooks/legacy/usePortal.tsx
index 1c08ee01..777628da 100644
--- a/packages/react/src/hooks/legacy/usePortal.tsx
+++ b/packages/react/src/hooks/legacy/usePortal.tsx
@@ -1,5 +1,5 @@
import React, { JSX, useEffect, useState } from 'react'
-import DefaultError from '../../components/Error'
+import DefaultError from '../../components/legacy/Error'
import Space from '../../components/legacy/LegacySpace'
import Spinner from '../../components/Spinner'
import {
diff --git a/packages/react/src/hooks/legacy/useSpace.tsx b/packages/react/src/hooks/legacy/useSpace.tsx
index ad2274d6..3fa351c3 100644
--- a/packages/react/src/hooks/legacy/useSpace.tsx
+++ b/packages/react/src/hooks/legacy/useSpace.tsx
@@ -1,5 +1,5 @@
import React, { JSX, useEffect, useState } from 'react'
-import DefaultError from '../../components/Error'
+import DefaultError from '../../components/legacy/Error'
import Space from '../../components/legacy/LegacySpace'
import Spinner from '../../components/Spinner'
import { State } from '@flatfile/embedded-utils'
diff --git a/packages/react/src/hooks/legacy/useSpaceTrigger.tsx b/packages/react/src/hooks/legacy/useSpaceTrigger.tsx
index 49d62058..712f17c8 100644
--- a/packages/react/src/hooks/legacy/useSpaceTrigger.tsx
+++ b/packages/react/src/hooks/legacy/useSpaceTrigger.tsx
@@ -1,5 +1,5 @@
import React, { JSX, useState } from 'react'
-import DefaultError from '../../components/Error'
+import DefaultError from '../../components/legacy/Error'
import Space from '../../components/legacy/LegacySpace'
import Spinner from '../../components/Spinner'
import { State } from '@flatfile/embedded-utils'
diff --git a/packages/react/src/hooks/useEvent.ts b/packages/react/src/hooks/useEvent.ts
index c8b510c3..a76a892f 100644
--- a/packages/react/src/hooks/useEvent.ts
+++ b/packages/react/src/hooks/useEvent.ts
@@ -1,6 +1,6 @@
import { EventCallback, FlatfileEvent } from '@flatfile/listener'
-import { useFlatfile } from './useFlatfile'
-import { useEffect } from 'react'
+import { useContext, useEffect } from 'react'
+import { FlatfileContext } from '../components'
// Overload definitions for better type checking
function useEvent(
@@ -24,7 +24,7 @@ function useEvent(
| any[] = [],
dependencies: any[] = []
) {
- const { listener } = useFlatfile()
+ const { listener } = useContext(FlatfileContext)
let callback: (event: any) => void | Promise
let actualDependencies: any[] = dependencies
@@ -47,7 +47,6 @@ function useEvent(
listener.on(eventType, callback)
}
- // Cleanup
return () => {
if (typeof filterOrCallback !== 'function') {
listener.off(eventType, filterOrCallback, callback)
diff --git a/packages/react/src/hooks/useFlatfile.ts b/packages/react/src/hooks/useFlatfile.ts
index 9a8274fe..da854067 100644
--- a/packages/react/src/hooks/useFlatfile.ts
+++ b/packages/react/src/hooks/useFlatfile.ts
@@ -68,7 +68,7 @@ export const useFlatfile: () => {
}
const openPortal = () => {
- if (publishableKey) {
+ if (publishableKey && !accessToken) {
handleCreateSpace()
} else if (accessToken) {
handleReUseSpace()
@@ -78,7 +78,7 @@ export const useFlatfile: () => {
const closePortal = () => {
setOpen(false)
- // TODO: Do we want to do any cleanup / remove the iFrame from the DOM?
+ // TODO: Do we want to do any cleanup / remove the iFrame/listener from the DOM?
}
return {
diff --git a/packages/react/src/hooks/useListener.ts b/packages/react/src/hooks/useListener.ts
index fcd1caa2..c0d1ff1f 100644
--- a/packages/react/src/hooks/useListener.ts
+++ b/packages/react/src/hooks/useListener.ts
@@ -1,26 +1,23 @@
-import { useEffect } from 'react'
-import { useFlatfile } from './useFlatfile'
+import { useContext, useEffect } from 'react'
import FlatfileListener from '@flatfile/listener'
+import { FlatfileContext } from '../components'
export function useListener(
cb: FlatfileListener | ((cb: FlatfileListener) => void),
dependencies: any[] = []
) {
- const { listener } = useFlatfile()
+ const { listener } = useContext(FlatfileContext)
useEffect(() => {
if (!listener) return
if (typeof cb === 'function') {
cb(listener)
} else {
- // Assume cb is a listener instance
listener.use(() => cb)
}
- // Call the callback with the listener to set up event handling
return () => {
- // Assuming 'detach' removes all event listeners from this instance
listener.detach()
}
- }, [...dependencies]) // React will re-run the effect if any dependencies change
+ }, [listener, cb, ...dependencies])
}
diff --git a/packages/react/src/hooks/usePlugin.ts b/packages/react/src/hooks/usePlugin.ts
index 2c496850..03f547db 100644
--- a/packages/react/src/hooks/usePlugin.ts
+++ b/packages/react/src/hooks/usePlugin.ts
@@ -1,20 +1,18 @@
-import { useEffect } from 'react'
-import { useFlatfile } from './useFlatfile'
+import { useContext, useEffect } from 'react'
import FlatfileListener from '@flatfile/listener'
+import { FlatfileContext } from '../components'
export function usePlugin(
plugin: (cb: FlatfileListener) => void,
dependencies: any[] = []
) {
- const { listener } = useFlatfile()
+ const { listener } = useContext(FlatfileContext)
useEffect(() => {
if (!listener) return
- // Call the callback with the listener to set up event handling
- listener.use(plugin)
+ listener.use(plugin)
return () => {
- // Assuming 'detach' removes all event listeners from this instance
listener.detach()
}
- }, [...dependencies]) // React will re-run the effect if any dependencies change
+ }, [listener, plugin, ...dependencies])
}
diff --git a/packages/react/src/types/IReactSpaceProps.ts b/packages/react/src/types/IReactSpaceProps.ts
index 07d44cf4..72516f18 100644
--- a/packages/react/src/types/IReactSpaceProps.ts
+++ b/packages/react/src/types/IReactSpaceProps.ts
@@ -1,7 +1,9 @@
import React from 'react'
import { ISpace } from '@flatfile/embedded-utils'
+import { Flatfile } from '@flatfile/api'
export type IReactSpaceProps = ISpace & {
error?: (error: Error | string) => React.ReactElement
loading?: React.ReactElement
+ sheets?: Flatfile.SheetConfig
}
diff --git a/packages/react/src/utils/_tests_/useDeepCompareEffect.spec.ts b/packages/react/src/utils/_tests_/useDeepCompareEffect.spec.ts
new file mode 100644
index 00000000..a45917e9
--- /dev/null
+++ b/packages/react/src/utils/_tests_/useDeepCompareEffect.spec.ts
@@ -0,0 +1,47 @@
+import { renderHook } from '@testing-library/react'
+import { useDeepCompareEffect } from '../useDeepCompareEffect'
+
+describe('useDeepCompareEffect', () => {
+ it('should call callback on initial render', () => {
+ const callback = jest.fn()
+ renderHook(() => useDeepCompareEffect(callback, [1, 2, 3]))
+
+ expect(callback).toHaveBeenCalledTimes(1)
+ })
+
+ it('should not call callback if dependencies do not change', () => {
+ const callback = jest.fn()
+ const { rerender } = renderHook(
+ ({ deps }) => useDeepCompareEffect(callback, deps),
+ { initialProps: { deps: [1, 2, 3] } }
+ )
+
+ rerender({ deps: [1, 2, 3] })
+ expect(callback).toHaveBeenCalledTimes(1)
+ })
+
+ it('should call callback when dependencies change', () => {
+ const callback = jest.fn()
+ const { rerender } = renderHook(
+ ({ deps }) => useDeepCompareEffect(callback, deps),
+ { initialProps: { deps: [1, 2, 3] } }
+ )
+
+ rerender({ deps: [1, 2, 4] })
+ expect(callback).toHaveBeenCalledTimes(2)
+ })
+
+ it('should call callback when dependencies deeply change', () => {
+ const callback = jest.fn()
+ const { rerender } = renderHook(
+ ({ deps }) => useDeepCompareEffect(callback, deps),
+ { initialProps: { deps: [{ key: 'value' }] } }
+ )
+
+ rerender({ deps: [{ key: 'value' }] })
+ expect(callback).toHaveBeenCalledTimes(1)
+
+ rerender({ deps: [{ key: 'new value' }] })
+ expect(callback).toHaveBeenCalledTimes(2)
+ })
+})
diff --git a/packages/react/src/utils/constants.ts b/packages/react/src/utils/constants.ts
index 365cca8d..8ea9d74a 100644
--- a/packages/react/src/utils/constants.ts
+++ b/packages/react/src/utils/constants.ts
@@ -1,9 +1,14 @@
import { Flatfile } from '@flatfile/api'
-export const workbookOnSubmitAction: Flatfile.Action = {
- operation: 'simpleSubmitAction',
- mode: 'foreground',
- label: 'Submit data',
- description: 'Action for handling data inside of onSubmit',
- primary: true,
+export const workbookOnSubmitAction = (sheetSlug?: string): Flatfile.Action => {
+ const operation = sheetSlug
+ ? `sheetSubmitAction-${sheetSlug}`
+ : 'workbookSubmitAction'
+ return {
+ operation,
+ mode: 'foreground',
+ label: 'Submit',
+ description: 'Action for handling data inside of onSubmit',
+ primary: true,
+ }
}
diff --git a/packages/react/src/utils/initializeSpace.tsx b/packages/react/src/utils/initializeSpace.tsx
index 15906d6c..a2975411 100644
--- a/packages/react/src/utils/initializeSpace.tsx
+++ b/packages/react/src/utils/initializeSpace.tsx
@@ -4,7 +4,7 @@ import { authenticate } from './authenticate'
import { getErrorMessage } from '@flatfile/embedded-utils'
export const initializeSpace = async (
- flatfileOptions: IReactSpaceProps & { sheets?: Flatfile.SheetConfig }
+ flatfileOptions: IReactSpaceProps
): Promise => {
let space
const {
@@ -46,7 +46,7 @@ export const initializeSpace = async (
...(languageOverride ? { languageOverride } : {}),
metadata: {
theme: themeConfig,
- sidebarConfig: sidebarConfig ? sidebarConfig : { showSidebar: false },
+ sidebarConfig: sidebarConfig || { showSidebar: false },
userInfo,
spaceInfo,
...(spaceBody?.metadata || {}),
diff --git a/packages/react/src/utils/useDeepCompareEffect.ts b/packages/react/src/utils/useDeepCompareEffect.ts
index 06b0e9b9..16f21584 100644
--- a/packages/react/src/utils/useDeepCompareEffect.ts
+++ b/packages/react/src/utils/useDeepCompareEffect.ts
@@ -11,5 +11,5 @@ export function useDeepCompareEffect(callback: () => void, dependencies: any) {
// Update the ref with current dependencies after running the callback
currentDependenciesRef.current = dependencies
- }, [callback, dependencies]) // You need to ensure callback and dependencies are stable or memoized
+ }, [callback, dependencies])
}
diff --git a/packages/react/src/utils/useEffectDebugger.ts b/packages/react/src/utils/useEffectDebugger.ts
new file mode 100644
index 00000000..19933d00
--- /dev/null
+++ b/packages/react/src/utils/useEffectDebugger.ts
@@ -0,0 +1,40 @@
+import { useRef, useEffect, EffectCallback, DependencyList } from 'react'
+
+const usePrevious = (value: any, initialValue: never[]) => {
+ const ref = useRef(initialValue)
+ useEffect(() => {
+ ref.current = value
+ })
+ return ref.current
+}
+export const useEffectDebugger = (
+ effectHook: EffectCallback,
+ dependencies: any[] | DependencyList | undefined,
+ dependencyNames = []
+) => {
+ const previousDeps = usePrevious(dependencies, [])
+
+ const changedDeps = dependencies?.reduce(
+ (acc: any, dependency: any, index: number) => {
+ if (dependency !== previousDeps[index]) {
+ const keyName = dependencyNames[index] || index
+ return {
+ ...acc,
+ [keyName]: {
+ before: previousDeps[index],
+ after: dependency,
+ },
+ }
+ }
+
+ return acc
+ },
+ {}
+ )
+
+ if (Object.keys(changedDeps).length) {
+ console.log('[use-effect-debugger] ', changedDeps)
+ }
+
+ useEffect(effectHook, dependencies)
+}
diff --git a/packages/react/src/vite-env.d.ts b/packages/react/src/vite-env.d.ts
deleted file mode 100644
index 11f02fe2..00000000
--- a/packages/react/src/vite-env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-///
diff --git a/packages/vue/src/components/SpaceC.vue b/packages/vue/src/components/SpaceC.vue
index db515611..418a74ca 100644
--- a/packages/vue/src/components/SpaceC.vue
+++ b/packages/vue/src/components/SpaceC.vue
@@ -56,8 +56,7 @@