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

11
我正在使用React Hook Form v7,尝试在页面重新加载后使我的数据表单持久化。我阅读了官方 RHF文档,建议使用little state machine,但我尝试实现它却没有成功。有更好的方法吗?然而...
使用它时遇到的第一个问题是,我的数据是一个复杂对象,所以updateAction不那么容易。
第二个问题是,我不知道何时以及如何触发updateAction来保存数据。应该在输入失去焦点时触发吗?还是在输入更改时触发?
这是我的测试代码:

Edit busy-rgb-ljbo55


你所说的“页面重新加载”,是指用户刷新整个应用程序吗? - Monstar
@HugoBp,“整个应用程序”是什么意思?我的意思是,如果用户单击浏览器刷新按钮,则数据是持久的。 - KaMZaTa
好的,我确实想知道你是否在谈论在应用程序中更改页面时持久化数据,因为这是你链接的RHF文档所讨论的内容。现在我明白这不是你想要的。 - Monstar
5个回答

7

我曾经遇到过这个问题,通过创建名为useLocalStorage的自定义Hook来解决它。但是,由于您正在使用React hook表单,这会使代码变得有些复杂且不够简洁!

我建议您使用轻量级包react-hook-form-persist。 您需要做的唯一工作是在useForm挂钩后添加useFormPersist挂钩。完成!

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")}
    />
    ...
  );
}

这对我来说不起作用,每次刷新页面后我都会得到默认值。 - Borjante
你必须验证你的代码。请检查这个示例:https://codesandbox.io/s/xpujc - Hamidreza Soltani
我把那个直接复制粘贴到我的项目里,但是不起作用。@Hamidreza 你有什么想法吗?我确保了包的版本是一样的。 - Borjante
开了一个 PR,代码在生产环境中可以工作,但在开发环境中使用 <React.StrictMode /> 时失败。 - Borjante
@Borjante 在看到你的代码之前不知道怎么做。你能通过 codesandbox 分享一下你的代码吗?你有按照我分享的示例代码来做吗?在我的项目中它运行得很好。 - Hamidreza Soltani

7
如果您想在本地存储中持久保存数据,下面是我达成目标的方法。
定义一个自定义钩子来持久保存数据。
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>
   )
}


请问您能否编辑我的 CodeSandbox 示例并向我展示如何在特定情况下进行操作?我尝试过了,但似乎无法与我的表单示例一起使用。我收到了很多“Uncaught TypeError: Cannot destructure property 'zip' of 'watch(...)' as it is undefined.”的错误提示。正如我所写的,我正在处理一个复杂的状态对象。 - KaMZaTa
我已经编辑了CodeSandbox,可以在这里尝试 https://codesandbox.io/embed/cool-nova-b8hsjs?fontsize=14&hidenavigation=1&theme=dark - kwaku hubert
我已经编辑了这篇文章以修复错误,你必须将对象字符串解析为JavaScript对象,否则它会抛出一个错误。 - kwaku hubert
谢谢,看起来它的工作非常顺利。 - KaMZaTa

1

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

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


谢谢您的回答,但它对我的示例无效(请检查我的 CodeSandbox)。我正在使用 RHF,我的 formData 是一个复杂对象。 - KaMZaTa
我的意思是,使用useEffect设置localStorage可能是个好主意,但我该如何更新整个RHF状态对象呢?我可以使用little-state-machine来监视每一个变化,但是我应该如何使用新的状态更新RHF状态呢? - KaMZaTa

0
感谢react-hook-form-persist
继承自@Hamidreza Soltani的答案。但是遇到了一些问题,当设置defaultValues时,页面重新加载将不会保留存储,而是使用defaultValues。因此,我为表单编写了一个包装器。
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

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>
    );
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接