-
-
Save SPodjasek/c5354da2e897daa14654674ab21c9b72 to your computer and use it in GitHub Desktop.
Formik-Autosave with internal state for holding submitted values and optional visible status presentation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useFormikContext } from 'formik'; | |
import debounce from 'lodash/debounce'; | |
import React, { useCallback, useEffect, useState } from 'react'; | |
import isEqual from 'react-fast-compare'; | |
type AutoSaveFieldsStates = 'changed' | 'saved' | undefined; | |
export type AutoSaveFieldsStatusRenderer = ( | |
state: 'submitting' | AutoSaveFieldsStates | |
) => React.ReactNode; | |
export interface AutoSaveFieldsProps { | |
/** | |
* Number of milliseconds to wait after last change before submitting the form | |
*/ | |
debounceMs?: number; | |
/** | |
* Should the status indicator be visible | |
*/ | |
visible?: boolean; | |
children?: React.ReactNode | AutoSaveFieldsStatusRenderer; | |
} | |
export interface AutoSaveFieldsState { | |
status?: AutoSaveFieldsStates; | |
values?: any; | |
} | |
export const AutoSaveFields = ({ | |
debounceMs = 1000, | |
visible = false, | |
children, | |
}: AutoSaveFieldsProps) => { | |
const formik = useFormikContext(); | |
const [{ status, values }, setStatus] = useState<AutoSaveFieldsState>({ | |
status: undefined, | |
values: formik.values, | |
}); | |
const debouncedSubmit = useCallback( | |
debounce(async (values) => { | |
await formik.submitForm(); | |
return setStatus({ status: 'saved', values }); | |
}, debounceMs), | |
[formik.submitForm, debounceMs] | |
); | |
useEffect(() => { | |
if ( | |
formik.isValid && | |
(formik.dirty || !isEqual(formik.values, values)) && | |
!formik.isSubmitting | |
) { | |
setStatus({ status: 'changed', values }); | |
debouncedSubmit(formik.values); | |
} | |
return debouncedSubmit.cancel; | |
}, [debouncedSubmit, formik.values]); | |
return typeof children === 'function' ? ( | |
(children as AutoSaveFieldsStatusRenderer)( | |
formik.isSubmitting ? 'submitting' : status | |
) | |
) : React.Children.count(children) !== 0 && status === 'saved' ? ( | |
React.Children.only(children) | |
) : visible ? ( | |
<p className="text-center text-success"> | |
{!!formik.isSubmitting | |
? 'Saving...' | |
: status === 'saved' | |
? 'Changes saved' | |
: null} | |
</p> | |
) : null; | |
}; |
Trying to implement this guy; struggling a bit.
"'AutoSaveFields' cannot be used as a JSX component.
Its return type 'string | number | boolean | Element | ReactFragment | null | undefined' is not a valid JSX element.
Type 'undefined' is not assignable to type 'Element | null'.ts(2786)'
Also
in console: "Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component"
Any thoughts? :)
<Formik initialValues={initialValues} onSubmit={onSubmit}>
<Form>
<Field name="firstName" />
<AutoSaveFields debounceMs={2000}>
{(status) => (
<FontAwesomeIcon fixedWidth icon={ status==="changed" ? faPen : status==="submitting" ? faCircleNotch : status==="saved" ? faSave : faHourglass } spin={status==="submitting" } />
)}
</AutoSaveFields>
</Form>
</Formik>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example usage with visible status: