React-admin: How to implement a autosave/background-save feature?
Asked Answered
T

2

8

How to implement a autosave/background-save feature using react-admin?

I want a feature when I am editing a rich input field, such as a rich text, a background saving will push my text to server, without touch my focus and edit position.

I tried using EditController and SimpleFrom, this will re-render form and get recorder from server, and lost focus and editing position. Any example or advice on this please?

Truckage answered 2/7, 2020 at 0:28 Comment(0)
B
2

I've built a drop-in component called EditFormAutoSave which you can simply add to an existing EditForm for auto-save to happen. It saves all changed "dirty" fields after a short wait interval of inactivity as well es when the edit form gets unmounted to prevent any data loss.

Important: The edit form needs to have redirect={false} set to prevent redirect from occurring after a successful auto-save.

Usage

const YourResourceEdit = props => (
  <Edit mutationMode="optimistic" {...props}>
    <SimpleForm redirect={false} toolbar={<EditToolbar />}>
      <EditFormAutoSave waitInterval={2000} />
      <TextInput source="someFieldName" />
      ...
    </SimpleForm>
  </Edit>
)

EditFormAutoSave.js

import _ from 'lodash'
import {useEffect, useRef} from 'react'
import {useEditContext} from 'react-admin'
import {useFormState} from 'react-final-form'

const EditFormAutoSave = ({waitInterval = 1000}) => {
  const {dirty, valid, values} = useFormState({subscription: {
    dirty: true, valid: true, values: true,
  }})
  const {save, saving} = useEditContext()
  const shouldSaveRef = useRef()
  const saveDebouncedRef = useRef()
  /*
   * Determine whether 'save' should be called by any of the following effects. Use a
   * 'ref' instead of a 'state' so that the an unmount effect can be set up which musn't
   * have state dependencies.
   */
  useEffect(() => {
    shouldSaveRef.current = dirty && valid && !saving
  }, [dirty, saving, valid])
  /*
   * Debounce the 'save()' function and store it in a 'ref' for the same reason as
   * above (it needs to be called on unmount which musn't have state dependencies).
   */
  useEffect(() => {
    saveDebouncedRef.current = _.debounce(save, waitInterval)
  }, [save, waitInterval])
  /*
   * Whenever the form data got dirty, schedule saving data
   */
  useEffect(() => {
    if (shouldSaveRef.current) {
      saveDebouncedRef.current({...values}, /* redirectTo= */false)
    }
  }, [dirty, saving, valid, values])
  /*
   * On component unmount submit any unsubmitted changes so that changed ("dirty") fields
   * get persisted. Said differently this effects prevents data loss of unsaved changes.
   */
  useEffect(() => () => {
    shouldSaveRef.current && saveDebouncedRef.current.flush()
  }, [])
}

export default EditFormAutoSave
Bailiff answered 12/1, 2022 at 12:19 Comment(0)
U
1

Based on the answer by @lars-blumberg, I've created one that uses typescript and works in react-admin version 4 that uses react-hook-form instead of react-final-form.

Important:

  • SimpleForm needs to have redirect={false} set to prevent redirect from occurring after a successful auto-save.
  • Edit/EditBase needs to have mutationMode="optimistic" (or "pessimistic") or else the EditFormAutoSave component won't be triggered to do the save.
import { useEffect, useRef } from 'react';
import { useEditContext } from 'react-admin';
import { useFormState, useWatch } from 'react-hook-form';
import { debounce } from 'lodash';

type Props = {
    waitInterval?: number;
};

const EditFormAutoSave = ({ waitInterval = 1000 }: Props) => {
    const { isDirty, isValid } = useFormState();
    const values = useWatch();
    const { save, saving } = useEditContext();
    const shouldSaveRef = useRef<boolean>();
    const saveDebouncedRef = useRef<any>();

    useEffect(() => {
        shouldSaveRef.current = isDirty && isValid && !saving;
    }, [isDirty, saving, isValid]);
    useEffect(() => {
        saveDebouncedRef.current = debounce(save, waitInterval);
    }, [save, waitInterval]);
    useEffect(() => {
        if (shouldSaveRef.current) {
            saveDebouncedRef.current({ ...values });
        }
    }, [isDirty, isValid, values]);

    useEffect(
        () => () => {
            shouldSaveRef.current && saveDebouncedRef.current.flush();
        },
        []
    );

    return null;
};

export default EditFormAutoSave;
Usurious answered 22/3, 2023 at 13:31 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.