Skip to content

Commit

Permalink
Merge pull request #520 from seamapi/init-hook
Browse files Browse the repository at this point in the history
  • Loading branch information
razor-x authored Oct 12, 2023
2 parents 66abe57 + 3a58c00 commit 314ef24
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 12 deletions.
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,51 @@ export function App() {

> Update the version in the script tag above with the exact version of this package you would like to use.
#### Web component attributes and properties

Each React component is defined as a custom element:

- The element name is in in kebab-case,
e.g., `<DeviceTable>` becomes `<seam-device-table>`.
- Each element is wrapped in a `<SeamProvider />`.
- An attribute and custom property is defined for each `<SeamProvider />` prop and component prop.
- Attributes are in kebab-case and properties are in snakeCase.

Attributes map directly to component props.
All attributes are passed as strings, thus non-string props have some limitations:

- Number props will be parsed using `parseFloat`.
- Boolean props should be passed as `true` or `false`, e.g., `disable-css-injection="true"` or `disable-css-injection="false"`.
- Array props may be passed as JSON, e.g., `device-ids="["foo", "bar"]"`,
or CSV, e.g., `device-ids="foo,bar"`.
- Function and object props should not be passed as attributes.
Set them as properties instead.

Use custom properties to work directly with JavaScript objects and primitives.

- This will avoid any issues with string parsing and serialization.
- Use the `onSessionUpdate` prop to maintain a reference to the internal Seam client.

For example,

```js
globalThis.customElements.whenDefined('seam-device-table').then(() => {
const elements = globalThis.document.getElementsByTagName('seam-device-table')
const element = elements[0]
if (element == null) {
throw new Error('Cannot find seam-device-table in document')
}
let seam
element.onSessionUpdate = (client) => {
seam = client
}
element.onDeviceClick = (deviceId) => {
if (seam == null) return
seam.devices.get({ device_id: deviceId }).then(console.log)
}
})
```

[Seam Console]: https://console.seam.co/

### React Hooks
Expand Down
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@
"@emotion/styled": "^11.10.6",
"@mui/icons-material": "^5.11.16",
"@mui/material": "^5.12.2",
"@rxfork/r2wc-react-to-web-component": "^2.3.0",
"@rxfork/r2wc-react-to-web-component": "^2.4.0",
"@seamapi/fake-seam-connect": "^1.17.0",
"@storybook/addon-designs": "^7.0.1",
"@storybook/addon-essentials": "^7.0.2",
Expand Down
5 changes: 4 additions & 1 deletion src/lib/element.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export type ElementProps<T> = R2wcProps<Omit<T, keyof CommonProps>>

type R2wcProps<T> = Record<
keyof T,
'string' | 'number' | 'boolean' | 'array' | 'function' | 'json' | 'object'
'string' | 'number' | 'boolean' | 'array' | 'object'
>

type ProviderProps = Omit<
Expand Down Expand Up @@ -59,6 +59,7 @@ const providerProps: R2wcProps<ProviderProps> = {
disableCssInjection: 'boolean',
disableFontInjection: 'boolean',
unminifiyCss: 'boolean',
onSessionUpdate: 'object',
}

export const defineCustomElement = ({
Expand Down Expand Up @@ -90,6 +91,7 @@ function withProvider<P extends JSX.IntrinsicAttributes>(
disableCssInjection,
disableFontInjection,
unminifiyCss,
onSessionUpdate,
container: _container,
...props
}: ProviderProps & { container: Container } & P): JSX.Element | null {
Expand All @@ -107,6 +109,7 @@ function withProvider<P extends JSX.IntrinsicAttributes>(
disableFontInjection ?? globalThis.disableSeamFontInjection
}
unminifiyCss={unminifiyCss ?? globalThis.unminifiySeamCss}
onSessionUpdate={onSessionUpdate}
>
<Component {...(props as P)} />
</SeamProvider>
Expand Down
19 changes: 17 additions & 2 deletions src/lib/seam/SeamProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
createContext,
type PropsWithChildren,
useContext,
useEffect,
useMemo,
} from 'react'
import type { Seam, SeamClientOptions } from 'seamapi'
Expand All @@ -16,6 +17,8 @@ import {
import { useSeamFont } from 'lib/seam/use-seam-font.js'
import { useSeamStyles } from 'lib/seam/use-seam-styles.js'

import { useSeamClient } from './use-seam-client.js'

declare global {
// eslint-disable-next-line no-var
var seam: SeamProviderProps | undefined
Expand Down Expand Up @@ -62,6 +65,7 @@ interface SeamProviderBaseProps extends PropsWithChildren {
unminifiyCss?: boolean | undefined
queryClient?: QueryClient | undefined
telemetryClient?: TelemetryClient | undefined
onSessionUpdate?: (client: Seam) => void
}

export type SeamProviderClientOptions = Pick<SeamClientOptions, 'endpoint'>
Expand All @@ -76,6 +80,7 @@ export function SeamProvider({
disableCssInjection = false,
disableFontInjection = false,
unminifiyCss = false,
onSessionUpdate = () => {},
queryClient,
telemetryClient,
...props
Expand Down Expand Up @@ -122,16 +127,26 @@ export function SeamProvider({
}
>
<Provider value={value}>
<Wrapper>{children}</Wrapper>
<Wrapper onSessionUpdate={onSessionUpdate}>{children}</Wrapper>
</Provider>
</QueryClientProvider>
</TelemetryProvider>
</div>
)
}

function Wrapper({ children }: PropsWithChildren): JSX.Element | null {
function Wrapper({
onSessionUpdate,
children,
}: Required<Pick<SeamProviderProps, 'onSessionUpdate'>> &
PropsWithChildren): JSX.Element | null {
useUserTelemetry()

const { client } = useSeamClient()
useEffect(() => {
if (client != null) onSessionUpdate(client)
}, [onSessionUpdate, client])

return <>{children}</>
}

Expand Down

0 comments on commit 314ef24

Please sign in to comment.