Skip to content

Instantly share code, notes, and snippets.

@jaredpalmer
Last active December 29, 2022 01:22
Show Gist options
  • Star 27 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save jaredpalmer/56e10cabe839747b84b81410839829be to your computer and use it in GitHub Desktop.
Save jaredpalmer/56e10cabe839747b84b81410839829be to your computer and use it in GitHub Desktop.
Formik-Autosave
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>
@wookiehunter
Copy link

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?

@SPodjasek
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment