React Hook Form 和页面重新加载的持久化数据

React Hook Form and persistent data on page reload

提问人:KaMZaTa 提问时间:3/17/2022 最后编辑:KaMZaTa 更新时间:10/22/2023 访问量:11246

问:

我正在使用 React Hook Form v7,我正在尝试使我的数据表单在页面重新加载时持久化。我阅读了 RHF 的官方文档,其中建议使用小状态机,我试图实现它,但没有成功。有没有更好的方法?然而。。。

我在使用它时遇到的第一个问题是,我的数据是一个复杂的对象,所以它应该不那么容易。updateAction

第二个问题是我不知道何时以及如何触发updateAction来保存数据。我应该在输入模糊时触发它吗?关于输入更改?

这是我的测试代码:

Edit busy-rgb-ljbo55

reactjs 持久性 react-hook-form

评论

0赞 Monstar 3/20/2022
通过页面重新加载,您的意思是用户是否刷新了整个应用程序?
0赞 KaMZaTa 3/20/2022
@HugoBp “整个应用程序”是什么意思?我的意思是,如果用户单击浏览器刷新按钮,则数据是持久的。
0赞 Monstar 3/20/2022
好的,我确实在问你是否在谈论在你的应用程序中保留页面更改的数据,因为这就是你链接的 RHF 文档所谈论的。现在我明白那不是你所追求的。

答:

1赞 Monstar 3/20/2022 #1

状态本身不会在页面重新加载时保留任何数据。
您需要将状态数据添加到本地存储。
然后将其加载回 on 状态(使用空依赖数组的 useEffect)。
componentDidMount

const Form = () => {
  const [formData, setFormData] = useState({})
  
  useEffect(() => {
    if(localStorage) {
      const formDataFromLocalStorage = localStorage.getItem('formData');
      if(formDataFromLocalStorage) {
        const formDataCopy = JSON.parse(formDataFromLocalStorage)
        setFormData({...formDataCopy})
      }
    }
  }, []);
  
  useEffect(() => {
    localStorage && localStorage.setItem("formData", JSON.stringify(formData))
  }, [formData]);
  
  const handleInputsChange = (e) => {
    setFormData({
      ...formData,
      [e.target.name]: e.target.value
    })
  }
  
  return (
    <div>
      <input 
        type="text" 
        name="firstName"
        placeholder='first name'
        onChange={e => handleInputsChange(e)}
        value={formData?.firstName}
      />
      <input 
        type="text" 
        name="lastName"
        placeholder='last name'
        onChange={e => handleInputsChange(e)}
        value={formData?.lastName}
      />
    </div>
  )
}

Edit gracious-hoover-oehb0w

评论

0赞 KaMZaTa 3/20/2022
感谢您的回答,但它不适用于我的例子(请检查我的 CodeSandbox)。我正在使用 RHF,我的 formData 是一个复杂的对象。
0赞 KaMZaTa 3/21/2022
我的意思是,使用 useEffect 设置 localStorage,我认为使用这可能是一个好主意,但是如何更新整个 RHF 状态对象?我可以使用 watch every change use,但随后我应该用新的 RHF 状态更新 RHF 状态。如何?little-state-machinemethods.watch()
6赞 kwaku hubert 3/22/2022 #2

如果坚持localStorage对你有用,这是我实现它的方式。

定义用于持久化数据的自定义钩子

export const usePersistForm = ({
  value,
  localStorageKey,
}) => {
  useEffect(() => {
    localStorage.setItem(localStorageKey, JSON.stringify(value));
  }, [value, localStorageKey]);

  return;
};

只需在表单组件中使用它即可

const FORM_DATA_KEY = "app_form_local_data";

export const AppForm = ({
  initialValues,
  handleFormSubmit,
}) => {
  // useCallback may not be needed, you can use a function
  // This was to improve performance since i was using modals
  const getSavedData = useCallback(() => {
    let data = localStorage.getItem(FORM_DATA_KEY);
    if (data) {
     // Parse it to a javaScript object
      try {
        data = JSON.parse(data);
      } catch (err) {
        console.log(err);
      }
      return data;
    }
    return initialValues;
  }, [initialValues]);

  const {
    handleSubmit,
    register,
    getValues,
    formState: { errors },
  } = useForm({ defaultValues: getSavedData() });
  const onSubmit: SubmitHandler = (data) => {
     // Clear from localStorage on submit
     // if this doesn’t work for you, you can use setTimeout
     // Better still you can clear on successful submission
    localStorage.removeItem(FORM_DATA_KEY);
    handleFormSubmit(data);
  };

  // getValues periodically retrieves the form data
  usePersistForm({ value: getValues(), localStorageKey: FORM_DATA_KEY });

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      ...
    </form>
   )
}

评论

0赞 KaMZaTa 3/25/2022
您能否编辑我的 CodeSandbox 示例并告诉我如何在该特定情况下执行此操作?我试过了,但它似乎不适用于我的表单示例。我收到了很多.正如我所写的,我正在处理一个复杂的状态对象。Uncaught TypeError: Cannot destructure property 'zip' of 'watch(...)' as it is undefined.
0赞 kwaku hubert 3/25/2022
我已经编辑了CodeSandbox,在这里试试 codesandbox.io/embed/...
0赞 kwaku hubert 3/25/2022
我已经编辑了它以修复错误,您必须将对象字符串解析为 javaScript 对象,否则它会抛出错误。
7赞 Hamidreza Soltani 7/28/2022 #3

我已经遇到了这个问题,并通过创建一个名为 .但是由于您使用的是 React 钩子表单,因此它使代码有点复杂且不太干净!useLocalStorage

我建议你简单地使用轻量级软件包 react-hook-form-persist。 你唯一需要做的就是一个接一个地添加钩子。做!useFormPersistuseForm

import { useForm } from "react-hook-form";
import useFormPersist from "react-hook-form-persist";

const yourComponent = () => {
  const {
    register,
    control,
    watch,
    setValue,
    handleSubmit,
    reset
  } = useForm({
    defaultValues: initialValues
  });

  useFormPersist("form-name", { watch, setValue });

  return (
    <TextField
      title='title'
      type="text"
      label='label'
      {...register("input-field-name")}
    />
    ...
  );
}

评论

0赞 Borjante 1/6/2023
这不知何故对我不起作用,刷新页面后我不断获得 defaultValues
0赞 Hamidreza Soltani 1/6/2023
您必须验证您的代码。请检查此示例:codesandbox.io/s/xpujc
0赞 Borjante 1/9/2023
从字面上复制粘贴到我的项目中,不起作用。有什么想法@Hamidreza吗?我确保软件包版本相同
0赞 Borjante 1/9/2023
github.com/tiaanduplessis/react-hook-form-persist/pull/32打开了一个 PR,代码在生产环境中工作,但在开发环境中使用 <React.StrictMode /> 失败
0赞 Hamidreza Soltani 1/10/2023
@Borjante 在看到您的代码之前一无所知。你能通过 Codesandbox 分享你的代码吗?您是否遵循了我已经分享的示例代码?它在我的项目中运行良好。
0赞 kychok98 8/23/2023 #4

react-hook-form-persist 干杯。

继承@Hamidreza索尔塔尼的答案。但是遇到了一些问题,当设置时,页面重新加载不会持久化存储,而是取而代之。所以,我为表单写了一个包装器。defaultValuesdefaultValues

export const usePersistForm = <
  TFieldValues extends FieldValues = FieldValues,
  TContext = any,
  TTransformedValues extends FieldValues | undefined = undefined
>(
  name: string,
  props?: UseFormProps<TFieldValues, TContext>
): UseFormReturn<TFieldValues, TContext, TTransformedValues> => {
  const hasStorage = window.sessionStorage.getItem(name)

  const form = useFormPrimitive<TFieldValues, TContext, TTransformedValues>({
    ...(hasStorage ? omit(props, ["defaultValues"]) : props),
  })

  useFormPersist(name, form)

  return form
}
0赞 Hao Zonggang 10/22/2023 #5

react-use 有一个钩子,在这个用例中派上用场。useLocalStorage

import { useLocalStorage } from 'react-use';

var Demo = () => {
    let [value, setValue, remove] = useLocalStorage('article', '',);

    let {
        register,
        handleSubmit,
    } = useForm({
        defaultValues: {
            article: value ?? '',
        },
    });

    let onsubmit: SubmitHandler<ArticleInputType> = (data) => {
        //...
        remove();
    };

    return (
        <form onSubmit={handleSubmit(onsubmit)}>
            <input
                {...register('article', {
                    required: 'This filed is required',
                    onChange: (e) => {
                        setValue(e.target.value);
                    },
                })}
            />
        </form>
    );
};