react-router-guard-dirty-state
Version:
Guard unsaved / dirty componets (usually containing forms) by alets when leaving both by onbeforeunload and react-roter routes.
205 lines (152 loc) • 5.7 kB
Markdown
# react-router-guard-dirty-state
*Guard unsaved / dirty componets (usually containing forms) by issuing alert when leaving the component both by closing window (onbeforeunload) and / or navigating away by using react-roter routes.*
## About
This is higher order component for React (DOM) to let end users know that view they are about to leave has *dirty state*.
*Leaving* means
1. navigating away from a route via *react-router* 4.
2. closing browser tab / window altogether
This helper was put together for augmenting 1st case as *react-router* [`<Promt>`](https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/Prompt.md) is fine and dandy, but does not cover closing browser window. However this HOC can be used also with generic components / containers, where no routing is used.
By *dirty state* it is usually (and also here) meant when some form data is changed and unsaved. But it can be any state that is not *resolved* by user before closing the window or navigating away from the route.
## Install
```sh
npm install react-router-guard-dirty-state --save-dev
```
## Usage
ES2015 (ES6)
```javascript
import guardDirtyState from 'react-router-guard-dirty-state';
```
CommonJS
```javascript
const guardDirtyState = require('react-router-guard-dirty-state').default;
```
Decorate
```javascript
/**
* @param {PropTypes.bool} receiveBoolProp default = true
* @param {PropTypes.node} React.Component (or React.PureComponent)
*/
guardDirtyState(receiveBoolProp)(React.Component)
```
## Props
```javascript
void guardDirtySetActive(bool)
```
On all cases where state become *dirty* issue `this.props.guardDirtySetActive(true)`.
On all cases where state becomes *pristine* (form is saved or reverted) issue `this.props.guardDirtySetActive(false)`.
When leaving page while *dirty* is active user will get promted.
```javascript
bool guardDirtyIsActive
```
This prop reflects current `dirty` state. Basically it means that issuing `this.props.guardDirtySetActive(isDirty)` where `isDirty` differs from previous *state* the component will receive prop update for `guardDirtyIsActive`. Do whatever you want with `this.props.guardDirtyIsActive`. Of course prop will be pushed only on *dirty state* change, not on every `guardDirtySetActive()`.
Example shows simple case where `SomeComponent` is
is under `<Router>` tree (any level deep as `react-router` 4 works [this way](https://reacttraining.com/react-router/web/example/basic) now) and we can use this prop to set wether `<Prompt />` should run on route change.
Note that receiving `guardDirtyIsActive` prop can be turned off by passing `false` to HOC if you do not use it (you do not use routes, or use some form management package for React that already handles `dirty` in component-space), which saves React reconciliation cycles.
```jsx
export default guardDirtyState(false)(SomeComponent);
```
## Example
```jsx
'use strict';
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {Prompt} from 'react-router-dom';
import guardDirtyState from 'react-router-guard-dirty-state';
class SomeComponent extends Component {
static propTypes = {
guardDirtySetActive: PropTypes.func,
guardDirtyIsActive: PropTypes.bool,
children: PropTypes.node
};
static defaultProps = {
guardDirtyIsActive: false
};
constructor (props) {
super(props);
this.state = {
formValues: {
name: '',
surname: ''
}
};
this.handleFormChange = this.handleFormChange.bind(this);
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.setFormDirty = this.setFormDirty.bind(this);
}
setFormDirty (isDirty = false) {
if (typeof this.props.guardDirtySetActive === 'function') {
this.props.guardDirtySetActive(isDirty);
}
}
handleFormChange (ev) {
this.setFormDirty(true);
const target = ev.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState((prevState, props) => ({
formValues: {
...prevState.formValues,
[name]: value
}
}));
}
handleFormSubmit (ev) {
const {formValues} = this.state;
alert(`Data was submitted. Name: ${formValues.name}, surname: ${formValues.surname}`);
ev.preventDefault();
this.setFormDirty(false);
}
render () {
const {formValues} = this.state;
return (
<div>
<h1>{'Submit your name and surname form'}</h1>
<hr/>
<form
onSubmit={this.handleFormSubmit}
>
<label>
{'Name:'}
<input
type="text"
name="name"
value={formValues.name}
onChange={this.handleFormChange}
/>
</label>
<label>
{'Surname:'}
<input
type="text"
name="surname"
value={formValues.surname}
onChange={this.handleFormChange}
/>
</label>
<input
type="submit"
value="Submit"
/>
</form>
<Prompt
when={this.props.guardDirtyIsActive}
message="Changes you made may not be saved / There is unsaved data. Are you sure you want to leave this page?"
/>
{this.props.children}
</div>
);
}
}
export default guardDirtyState()(SomeComponent);
```
## Building
```sh
npm run build:dev
```
```sh
npm run build:prod
```
## Testing
N/A
## Todo
Copy possible static methods using [hoist-non-react-statics](https://github.com/mridgway/hoist-non-react-statics)