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 @@