-
-
Save jaredpalmer/56e10cabe839747b84b81410839829be to your computer and use it in GitHub Desktop.
import React from 'react'; | |
import PropTypes from 'prop-types' | |
import debounce from 'lodash.debounce' // or whatevs | |
import isEqual from 'lodash.isEqual' | |
class AutoSave extends React.Component { | |
static contextTypes = { | |
formik: PropTypes.object | |
} | |
state = { | |
isSaving: false, | |
} | |
componentWillReceiveProps(nextProps, nextContext) { | |
if (!isEqual(nextProps.values, this.props.values)) { | |
this.save() | |
} | |
} | |
save = debounce(() => { | |
this.setState({ isSaving: true, saveError: undefined }) | |
this.props.onSave(this.props.values) | |
.then( | |
() => this.setState({ isSaving: false, lastSaved: new Date() }), | |
() => this.setState({ isSaving: false, saveError }) | |
) | |
}), 300) | |
} | |
render() { | |
return this.props.render(this.state) | |
} | |
} | |
} | |
// Usage | |
import React from 'react'; | |
import { Formik, Field, Form } from 'formik' | |
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now' | |
const App = () => | |
<div> | |
<h1>Signup form</h1> | |
<Formik | |
initialValues={{ firstName: '', lastName: ''} | |
onSubmit={values => { | |
setTimeout(() => { | |
alert(JSON.stringify(values, null,2)) | |
}, 500) | |
} | |
render={() => | |
<Form> | |
<Field name="firstName" /> | |
<Field name="lastName" /> | |
<button type="submit">Submit</button> | |
<AutoSave | |
onSave={values => CallMyApi(values) /* must return a promise 😎 */}\ | |
debounce={1000} | |
render={({isSaving, lastSaved, saveError }) => | |
<div> | |
{!!isSaving | |
? <Spinner/> | |
: !!saveError | |
? `Error: ${saveError}` | |
: lastSaved | |
? `Autosaved ${distanceInWordsToNow(lastSaved)} ago` | |
: 'Changes not saved'} | |
</div> | |
} | |
/> | |
</Form> | |
} | |
/> | |
</div> |
I am using this to autosave, showing two different messages for success and error. I hope it would be helpful for you.
import { AkCheck, AkError } from '@akelius-con/react-ui-kit-icons';
import { Box, createStyles, makeStyles, Typography } from '@material-ui/core';
import cn from 'classnames';
import { FormikProps } from 'formik';
import { debounce, isEqual } from 'lodash';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
interface Props {
formik: FormikProps<any>;
debounceMs?: number;
error: null | boolean;
}
const FormikAutoSave = ({ formik, debounceMs = 1500, error }: Props) => {
const classes = useStyles();
const { t } = useTranslation();
const debouncedSubmit = useCallback(
debounce(() => {
if (!formik.isValid || !formik.dirty) return false;
return formik.submitForm();
}, debounceMs),
[formik.submitForm, formik.isValid, formik.initialValues, formik.values, debounceMs],
);
useEffect(() => {
debouncedSubmit();
return debouncedSubmit.cancel;
}, [debouncedSubmit, formik.values]);
return (
<div className="spinner">
{formik.isSubmitting && (
<div>saving in progress</div>
)}
{!formik.isSubmitting && (
<div>
{error === false && (
<>
Success
</>
)}
{error === true && (
<>
Error
</>
)}
</div>
)}
</div>
);
};
export default FormikAutoSave;
// usages: <FormikAutoSave error={autoSaveError} formik={formik} />
Hi, I am working on some code that uses this for saving filter values. When I select a record in a filtered list and then return to the list, using browser back button, only 2 of the 4 filter values are applied. Has anyone else come across this and resolved?
There's one tiny problem with the solutions above - when you rely solely on formik.dirty
, you will miss any changes that result in a field value equal to initialValues
, as per specs:
dirty: boolean
Returns true if values are not deeply equal from initial values, false otherwise. dirty is a readonly computed property and should not be mutated directly.
So when your Formik doesn't have enableReinitialize
prop enabled (by default, it doesn't) and you change some field, autosave submits your form, and then you change that field once more to value equal with initial value - it won't detect that as a change.
For most implementations, that may not be the case, or maybe you can use enableReinitialize
- but I didn't want to, as users can lose the focused field when reinitialization happens. So I came up with another implementation, which holds the last submitted values in its state, and uses isEqual
from react-fast-compare
just as Formik internally does.
You can find this implementation here: https://gist.github.com/SPodjasek/c5354da2e897daa14654674ab21c9b72
Oh I am able to get rid of the issue. Just passed formik.values as dependencies instead of formik. It works. Thanks for sharing the information