react-hook-form 和 useState(切换)

react-hook-form and useState (toggle)

我有以下用例:

用户想要切换配置文件是否处于活动状态。

配置: Next.js, 动物群数据库, 反应挂钩形式

我使用 useState 更改切换状态,并使用 react-hook-forms 将其他值发送到我的 Fauna 数据库和切换状态。我希望切换器具有来自数据库的状态,当用户切换它然后按下提交按钮时,我想更改数据库中的状态。

我似乎无法在切换时将正确的状态发送回数据库。

主要成分:

export default function Component() {
 const [status, setStatus] = useState(
    userData?.profileStatus ? userData.profileStatus : false
  );

const defaultValues = {
    profileStatus: status ? userData?.profileStatus : false
  };

const { register, handleSubmit } = useForm({ defaultValues });

  const handleUpdateUser = async (data) => {

    const {
      profileStatus
    } = data;
    try {
      await fetch('/api/updateProfile', {
        method: 'PUT',
        body: JSON.stringify({
          profileStatus
        }),
        headers: {
          'Content-Type': 'application/json'
        }
      });
      alert(`submitted data: ${JSON.stringify(data)}`);
    } catch (err) {
      console.error(err);
    }
  };

return (
  <div>
    <form onSubmit={handleSubmit(handleUpdateUser)}>
      <Toggle status={status} setStatus={setStatus} />
      <button type="submit">
        Save
      </button>
    </form>
  </div>
 )
}

切换组件:

import { Switch } from '@headlessui/react';

function classNames(...classes) {
  return classes.filter(Boolean).join(' ');
}

export default function Toggle({status , setStatus}) {

  return (
    <Switch.Group as="div" className="flex items-center justify-between">
      <span className="flex-grow flex flex-col">
        <Switch.Label
          as="span"
          className="text-sm font-medium text-gray-900"
          passive
        >
          Profilstatus
        </Switch.Label>
        <Switch.Description as="span" className="text-sm text-gray-500 w-44">
          Her sætter du om din profil skal være aktiv eller inaktiv.
        </Switch.Description>
      </span>
      <Switch
        checked={status}
        onChange={setStatus}
        className={classNames(
          status ? 'bg-blue-600' : 'bg-gray-200',
          'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500'
        )}
      >
        <span
          aria-hidden="true"
          className={classNames(
            status ? 'translate-x-5' : 'translate-x-0',
            'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200'
          )}
        />
      </Switch>
    </Switch.Group>
  );
}

updateProfile.js

import { updateProfileInfo } from '@/utils/Fauna';
import { getSession } from 'next-auth/react';

export default async (req, res) => {
  const session = await getSession({ req });
  if (!session) return res.status(401);

  const userId = session.user.id;
  if (req.method !== 'PUT') {
    return res.status(405).json({ msg: 'Method not allowed' });
  }

  const {
    profileStatus,
    image,
    about,
    preferences,
    socialmedia
   } = req.body;
  try {
    const updated = await updateProfileInfo(
      userId,
      profileStatus,
      image,
      about,
      preferences,
      socialmedia
    );
    return res.status(200).json(updated);
  } catch (err) {
    console.error(err);
    res.status(500).json({ msg: 'Something went wrong.' });
  }
  res.end();
};

Fauna.js

const updateProfileInfo = async (
  userId,
  profileStatus,
  image,
  about,
  preferences,
  socialmedia
) => {
  return await faunaClient.query(
    q.Update(q.Ref(q.Collection('users'), userId), {
      data: {
        profileStatus,
        image,
        about,
        preferences,
        socialmedia
      }
    })
  );
};

module.exports = {
  updateProfileInfo

}

你们能看出我做错了什么吗?

我制作了一个小沙箱来演示如何使用 react-hook-form 来实现您的用例。

它不起作用的原因是,您在切换开关时永远不会更新 react-hook-form 的内部状态,您只会更新您的 useState。因此,当您调用 handleUpdateUser 时,作为参数传递的数据是您通过 defaultValues.

设置的初始数据

其实这里不用useState,直接用react-hook-form的内部表单状态即可。为此,您必须使用 <Controller /> 组件 react-hook-form 提供的 <Switch /> 组件来自 Headless UI @headlessui/react 是一个外部控制组件,它不为实际的 <input /> 元素公开一个 ref 道具(<Switch /> 使用 <button /> 而不是 <input /> 元素)。您可以找到更多信息 here.

通过这种方式,您还可以通过提供 valueonChange 道具而不是 status 和 [=32=,使您的 <Toggle /> 更通用以供重用].当然,您仍然可以使用这些名称。 <Controller /> 将在 field 对象上提供 valueonChange 道具,我将其传播到 <Toggle /> 组件上。

在您的示例中,您的 <Component /> 组件将如何接收初始 userData 并不清楚。我假设你会提出 api 请求,所以我把它放在 useEffect 中。要在 api 调用完成后更新表单状态,您必须使用 react-hook-form 提供的 reset 方法。如果您只在 userData 已加载时渲染 <Component />,则可以省略此步骤,只需将结果传递给 defaultValuesuseForm

我用一个简单的 Promise 模拟了 api 调用,但你应该明白了。

Component.js

import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import Toggle from "./Toggle";

// Server Mock
let databaseState = {
  profileStatus: true
};

const getUserData = () => Promise.resolve(databaseState);
const updateUserData = (newState) => {
  databaseState = newState;

  return Promise.resolve(newState);
};

function Component() {
  const { control, reset, handleSubmit } = useForm({
    defaultValues: { profileStatus: false }
  });

  useEffect(() => {
    const loadData = async () => {
      const result = await getUserData();

      reset(result);
    };

    loadData();
  }, [reset]);

  const handleUpdateUser = async (data) => {
    try {
      const result = await updateUserData(data);

      console.log(result);
    } catch (err) {
      console.error(err);
    }
  };

  return (
    <div>
      <form onSubmit={handleSubmit(handleUpdateUser)}>
        <Controller
          control={control}
          name="profileStatus"
          render={({ field: { ref, ...field } }) => <Toggle {...field} />}
        />
        <button type="submit">Save</button>
      </form>
    </div>
  );
}

Toggle.js

import { Switch } from "@headlessui/react";

function classNames(...classes) {
  return classes.filter(Boolean).join(" ");
}

export default function Toggle({ value, onChange }) {
  return (
    <Switch.Group as="div" className="flex items-center justify-between">
      <span className="flex-grow flex flex-col">
        <Switch.Label
          as="span"
          className="text-sm font-medium text-gray-900"
          passive
        >
          Profilstatus
        </Switch.Label>
        <Switch.Description as="span" className="text-sm text-gray-500 w-44">
          Her sætter du om din profil skal være aktiv eller inaktiv.
        </Switch.Description>
      </span>
      <Switch
        checked={value}
        onChange={onChange}
        className={classNames(
          value ? "bg-blue-600" : "bg-gray-200",
          "relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
        )}
      >
        <span
          aria-hidden="true"
          className={classNames(
            value ? "translate-x-5" : "translate-x-0",
            "pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"
          )}
        />
      </Switch>
    </Switch.Group>
  );
}