My app has form with <AutoSave/>
component. This component calls submit once form values were changed. Everything works well but when changing the route it changes form values and <AutoSave/>
calls submit. How to solve this problem? A possible solution is to mount <AutoSave/>
again when changing the route.
Codesandbox
AutoSave:
import React, { useEffect, useCallback } from 'react'
import { useFormikContext } from 'formik'
import debounce from 'lodash.debounce'
const AutoSave = ({ debounceMs }) => {
const formik = useFormikContext()
const debouncedSubmit = useCallback(
debounce(formik.submitForm, debounceMs),
[formik.submitForm, debounceMs]
)
useEffect(() => debouncedSubmit, [debouncedSubmit, formik.values])
return <>{!!formik.isSubmitting && "saving..."}</>
}
My app:
const App: FC = () => {
const {books} = getBooks() // [{id: 1, title: 'test', summary: 'test'}, ...]
const {query} = useRouter()
const handleSubmit = useCallback(async values => {
try {
await API.patch('/books', {id: query.book, ...values})
} catch (e) {}
}, [query.book])
return (
<>
<span>Books</span>
{books.map(({id, title}, key) => (
<Link key={key} href='/book/[book]' as={`/book/${id}`}>
<a>{title}</a>
</Link>
))}
{query.book && (
<MainForm
book={books.find(book => book.id === query.book)}
handleSubmit={handleSubmit}/>
)}
</>
)
}
MainForm:
type Props = {
book: BookProps // {id: string, title: string ...},
handleSubmit: (values) => Promise<void>
}
const MainForm: FC<Props> = ({book, handleSubmit}) => (
<Formik
enableReinitialize
initialValues={{title: book.title, summary: book.summary}}
handleSubmit={values => handleSubmit(values)}>
{() => (
<Form>
//...My fields...
<AutoSave debounceMs={500}/> // <=== AutoSave with debounce
</Form>
)}
</Formik>
)
Check it out: https://codesandbox.io/s/clever-sun-057vy
# Problem
useEffect(() => debouncedSubmit, [debouncedSubmit, formik.values]);
formik.values
will always change even when the component mounts. That is why debouncedSubmit
gets called on route change.
So basically, we don't want to run it as component first rendering but when the form is made changes by user.
formik.dirty
is the key. Just check for formik.dirty
before doing submit.
const AutoSave = ({ debounceMs }) => {
const formik = useFormikContext();
const debouncedSubmit = useCallback(
debounce(formik.submitForm, debounceMs),
[formik.submitForm, debounceMs]
);
useEffect(() => {
formik.dirty && debouncedSubmit();
}, [debouncedSubmit, formik.dirty, formik.values]);
return <>{!!formik.isSubmitting && 'saving...'}</>;
};
Another thing is the Formik instance. This Formik
will be used for all the books.
So, you will need to reset the form when binding a new book into it, by using enableReinitialize
prop.
<Formik
enableReinitialize
initialValues={{ title: book.title, summary: book.summary, id: book.id }}
onSubmit={values => handleSubmit(values)}
>
Or use seperated instances for each book with key={book.id}
<Formik
key={book.id}
initialValues={{ title: book.title, summary: book.summary, id: book.id }}
onSubmit={values => handleSubmit(values)}
>
You need to have something like firstSubmit
, where you check if firstSubmit
already happened, so it only calls AutoSave
on the second submit (where it actually changed).
const AutoSave = ({debounceMs}) => {
const [firstSubmit, setFirstSubmit] = React.useState(false)
const formik = useFormikContext();
const debouncedSubmit = React.useCallback(
debounce(firstSubmit ? formik.submitForm : () => setFirstSubmit(true), debounceMs),
[debounceMs, formik.submitForm, firstSubmit, setFirstSubmit]
);
React.useEffect(debouncedSubmit , [debouncedSubmit, formik.values]);
return <>{!!formik.isSubmitting ? 'saving...' : null}</>;
}
I'm not sure if the code works, I haven't tested it yet, because I'm not sure where debounce
comes from, but the logic is that.
You should check if it already have submited once, and if so, skip it, only submiting when is the second time.
If you provide a working example, I can test it and make it work if the code above doens't work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With