Skip to content

Instantly share code, notes, and snippets.

@SPodjasek
Forked from jaredpalmer/Formik-Autosave.jsx
Last active August 2, 2022 14:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SPodjasek/c5354da2e897daa14654674ab21c9b72 to your computer and use it in GitHub Desktop.
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
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;
};
@SPodjasek
Copy link
Author

Example usage with visible status:

<AutoSaveFields debounceMs={2000}>
  {(status) => (
    <FontAwesomeIcon
      fixedWidth
      icon={
        status === "changed"
          ? faPen
          : status === "submitting"
          ? faCircleNotch
          : status === "saved"
          ? faSave
          : faHourglass
      }
      spin={status === "submitting"}
    />
  )}
</AutoSaveFields>

@perkins2099
Copy link

perkins2099 commented Aug 2, 2022

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