Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding optional prop for lazy load the intercom script #236

Merged
merged 16 commits into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Place the `IntercomProvider` as high as possible in your application. This will
| onUnreadCountChange | (number) => void | triggered when the current number of unread messages changes | false | |
| shouldInitialize | boolean | indicates if the Intercom should be initialized. Can be used in multistaged environment | false | true |
| apiBase | string | If you need to route your Messenger requests through a different endpoint than the default. Generally speaking, this is not needed.<br/> Format: `https://${INTERCOM_APP_ID}.intercom-messenger.com` (See: [https://github.com/devrnt/react-use-intercom/pull/96](https://github.com/devrnt/react-use-intercom/pull/96)) | false | |
| initializeDelay | number | Indicates if the intercom initialization should be delayed, delay is in ms, defaults to 0. See https://github.com/devrnt/react-use-intercom/pull/236 | false | |

#### Example
```javascript
Expand Down Expand Up @@ -239,6 +240,15 @@ These props are `JavaScript` 'friendly', so [camelCase](https://en.wikipedia.org
> Mind that all the properties in `react-use-intercom` are camel cased, except for the `customAttributes` property in the `boot` and `update` method from `useIntercom`.

## Advanced

### Delay initialization

`<IntercomProvider />` uses an official intercom snippet and is directly initialized on load. In the background this snippet will load some external code that makes Intercom work. All of this magic happens on the initial load and in some use cases this can become problematic (E.g. when LCP is priority).

Since [v1.2.0](https://github.com/devrnt/react-use-intercom/releases/tag/v1.2.0) it's possible to delay this initialisation by passing `initializeDelay` in `<IntercomProvider />` (it's in milliseconds). However most of the users won't need to mess with this.

For reference see https://github.com/devrnt/react-use-intercom/pull/236 and https://forum.intercom.com/s/question/0D52G00004WxWLs/can-i-delay-loading-intercom-on-my-site-to-reduce-the-js-load
### useCallback
To reduce the amount of re-renders in your React application I suggest to make use of [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback)

**TLDR:** `useCallback` will return a memoized version of the callback that only changes if one of the dependencies has changed.
Expand Down
5 changes: 5 additions & 0 deletions playground/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ProviderEventsPage,
ProviderApiPage,
UseIntercomTourPage,
UseIntercomWithDelay
} from './modules';

import { Page, Style } from './modules/common';
Expand Down Expand Up @@ -47,6 +48,7 @@ const App = () => {
<Route path="/providerApi" component={ProviderApiPage} />
<Route path="/useIntercom" component={UseIntercomPage} />
<Route path="/useIntercomTour" component={UseIntercomTourPage} />
<Route path="/useIntercomWithTimeout" component={UseIntercomWithDelay} />
<Route path="/" exact>
<Navigation>
<Link to="/provider">
Expand All @@ -61,6 +63,9 @@ const App = () => {
<Link to="/useIntercomTour">
<code>useIntercom with tour</code>
</Link>
<Link to="/useIntercomWithTimeout">
<code>useIntercom with delayed boot</code>
</Link>
</Navigation>
</Route>
</Router>
Expand Down
1 change: 1 addition & 0 deletions playground/modules/useIntercom/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as UseIntercomPage } from './useIntercom';
export { default as UseIntercomTourPage } from './useIntercomTour';
export { default as UseIntercomWithDelay } from './useIntercomWithDelay'
42 changes: 42 additions & 0 deletions playground/modules/useIntercom/useIntercomWithDelay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as React from 'react';
import styled from 'styled-components';

import { IntercomProvider } from '../../../.';

const Grid = styled.div`
display: grid;
grid-template-columns: repeat(1, 1fr);
width: 100%;
`;

const Item = styled.div`
display: grid;
grid-template-rows: min-content;

&::after {
content: '';
margin: 2rem 0 1.5rem;
border-bottom: 2px solid var(--grey);
width: 100%;
}
`;

const RawUseIntercomPage = () => {
return (
<Grid>
<Item>
<p>Intercom will be initialized (and autobooted) after 5000ms</p>
</Item>
</Grid>
);
};

const UseIntercomWithDelayPage = () => {
return (
<IntercomProvider appId="jcabc7e3" initializeDelay={5000} autoBoot>
<RawUseIntercomPage />
</IntercomProvider>
);
};

export default UseIntercomWithDelayPage;
17 changes: 10 additions & 7 deletions src/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
* Snippet to initialize the Intercom instance
*
* @param appId - Intercom app id
* @param [timeout=0] - Amount of milliseconds that the initialization should be delayed, defaults to 0
*
* @see {@link https://developers.intercom.com/installing-intercom/docs/basic-javascript}
*/
const initialize = (appId: string) => {
const initialize = (appId: string, timeout = 0) => {
var w = window;
var ic = w.Intercom;
if (typeof ic === 'function') {
Expand All @@ -23,12 +24,14 @@ const initialize = (appId: string) => {
};
w.Intercom = i;
var l = function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(function() {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/' + appId;
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
}, timeout);
};
if (document.readyState === 'complete') {
l();
Expand Down
12 changes: 8 additions & 4 deletions src/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
onUnreadCountChange,
shouldInitialize = !isSSR,
apiBase,
initializeDelay,
...rest
}) => {
const isBooted = React.useRef(autoBoot);
Expand All @@ -36,7 +37,7 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
);

if (!isSSR && !window.Intercom && shouldInitialize) {
initialize(appId);
initialize(appId, initializeDelay);
// Only add listeners on initialization
if (onHide) IntercomAPI('onHide', onHide);
if (onShow) IntercomAPI('onShow', onShow);
Expand All @@ -56,7 +57,10 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({
}

const ensureIntercom = React.useCallback(
(functionName: string = 'A function', callback: Function) => {
(
functionName: string = 'A function',
callback: (() => void) | (() => string),
) => {
if (!window.Intercom && !shouldInitialize) {
logger.log(
'warn',
Expand Down Expand Up @@ -173,8 +177,8 @@ export const IntercomProvider: React.FC<IntercomProviderProps> = ({

const getVisitorId = React.useCallback(() => {
return ensureIntercom('getVisitorId', () => {
return (IntercomAPI('getVisitorId') as unknown) as string;
});
return IntercomAPI('getVisitorId');
}) as string;
}, [ensureIntercom]);

const startTour = React.useCallback(
Expand Down
6 changes: 6 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,4 +419,10 @@ export type IntercomProviderProps = {
* Format https://${INTERCOM_APP_ID}.intercom-messenger.com
*/
apiBase?: string;
/**
* Indicates if the intercom initialization should be delayed, delay is in ms
*
* @remarks If not set delay is set to 0ms
* */
initializeDelay?: number;
};
26 changes: 26 additions & 0 deletions test/useIntercom.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,30 @@ describe('useIntercom', () => {

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});

test('should await a certain amount on delayed initialization', async () => {
const { result, waitFor } = renderHook(() => useIntercom(), {
wrapper: ({ children }) => (
<IntercomProvider appId={INTERCOM_APP_ID} initializeDelay={5000}>
{children}
</IntercomProvider>
),
});

const { boot } = result.current;

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: undefined });

await waitFor(() => {}, { timeout: 5000 });

act(() => {
boot();
});

expect(window.intercomSettings).toEqual({ app_id: INTERCOM_APP_ID });
});
});