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

feat(useBeforeUnload): allow passing a dirty function (#842) #867

Merged
merged 1 commit into from
Feb 15, 2020
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
27 changes: 27 additions & 0 deletions docs/useBeforeUnload.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ React side-effect hook that shows browser alert when user try to reload or close

## Usage

### Boolean check

```jsx
import {useBeforeUnload} from 'react-use';

Expand All @@ -20,3 +22,28 @@ const Demo = () => {
);
};
```

### Function check

Note: Since every `dirtyFn` change registers a new callback, you should use
[refs](https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback)
if your test value changes often.

```jsx
import {useBeforeUnload} from 'react-use';

const Demo = () => {
const [dirty, toggleDirty] = useToggle(false);
const dirtyFn = useCallback(() => {
return dirty;
}, [dirty]);
useBeforeUnload(dirtyFn, 'You have unsaved changes, are you sure?');

return (
<div>
{dirty && <p>Try to reload or close tab</p>}
<button onClick={() => toggleDirty()}>{dirty ? 'Disable' : 'Enable'}</button>
</div>
);
};
```
27 changes: 18 additions & 9 deletions src/useBeforeUnload.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
import { useEffect } from 'react';
import { useCallback, useEffect } from 'react';

const useBeforeUnload = (enabled: boolean = true, message?: string) => {
useEffect(() => {
if (!enabled) {
return;
}
const useBeforeUnload = (enabled: boolean | (() => boolean) = true, message?: string) => {
const handler = useCallback(
(event: BeforeUnloadEvent) => {
const finalEnabled = typeof enabled === 'function' ? enabled() : true;

if (!finalEnabled) {
return;
}

const handler = (event: BeforeUnloadEvent) => {
event.preventDefault();

if (message) {
event.returnValue = message;
}

return message;
};
},
[enabled, message]
);

useEffect(() => {
if (!enabled) {
return;
}

window.addEventListener('beforeunload', handler);

return () => window.removeEventListener('beforeunload', handler);
}, [message, enabled]);
}, [enabled, handler]);
};

export default useBeforeUnload;
22 changes: 19 additions & 3 deletions stories/useBeforeUnload.story.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { storiesOf } from '@storybook/react';
import * as React from 'react';
import React, { useCallback } from 'react';
import { useBeforeUnload, useToggle } from '../src';
import ShowDocs from './util/ShowDocs';

const Demo = () => {
const DemoBool = () => {
const [dirty, toggleDirty] = useToggle(false);
useBeforeUnload(dirty, 'You have unsaved changes, are you sure?');

Expand All @@ -15,6 +15,22 @@ const Demo = () => {
);
};

const DemoFunc = () => {
const [dirty, toggleDirty] = useToggle(false);
const dirtyFn = useCallback(() => {
return dirty;
}, [dirty]);
useBeforeUnload(dirtyFn, 'You have unsaved changes, are you sure?');

return (
<div>
{dirty && <p>Try to reload or close tab</p>}
<button onClick={() => toggleDirty()}>{dirty ? 'Disable' : 'Enable'}</button>
</div>
);
};

storiesOf('Side effects|useBeforeUnload', module)
.add('Docs', () => <ShowDocs md={require('../docs/useBeforeUnload.md')} />)
.add('Demo', () => <Demo />);
.add('Demo (boolean)', () => <DemoBool />)
.add('Demo (function)', () => <DemoFunc />);