一个反应反冲原子中的多个形式值相互覆盖

Multiple form values in one react recoil atom override each other

有没有办法在一个 React Recoil 原子中保存多个表单输入值?我一直尝试添加 2 个表单字段值,但它们只是相互覆盖。

我有一个包含 2 个字段的注册表;电子邮件和 phone.

我的(简化的)表单组件看起来像这样;

import { atom, useSetRecoilState, useRecoilValue } from 'recoil';

const registerAtom = atom({
    key: 'register',
    default: [],
});

function Registration() {
    const setEmail = useSetRecoilState(registerAtom);
    const email = useRecoilValue(registerAtom);

    const setPhone = useSetRecoilState(registerAtom);
    const phone = useRecoilValue(registerAtom);

    return (
        <>
            <form>
                <input name="email" type="text" className="form-control" value={email} onChange={e => setEmail(e.target.value)} placeholder="Email Address" />
                <input name="phone" type="text" className="form-control" value={phone} onChange={e => setPhone(e.target.value)} placeholder="Phone Number" />
            </form>
        </>
    )
}

你的 atom 有一个单一的值 register 在开始时保存一个数组,然后用输入的值赋值。

两个 input 都设置原子的状态 registerAtom,使其相互覆盖。

您需要做的是持有一个对象作为 register 的值,它有两个键:emailphone。然后,您可以使用已更改的特定 input 中的相关值更新每个键。

所以。而不是:

const registerAtom = atom({
    key: 'register',
    default: [],
});

创建这个 atom:

const registerAtom = atom({
    key: 'register',
    default: {
        email: '',
        phone: ''
    },
});

这为 emailphone 创建了初始值为空字符串的对象。

现在您可以这样定义 set 函数:

const setRegistrationInfo = useSetRecoilState(registerAtom);
const registrationInfo = useRecoilValue(registerAtom);

最后,您需要做的就是在设置状态时更改对象的特定键。确保你正在创建一个新的 Object 因为你正在更新一个状态并且状态需要一个新的更新对象,所以我们将使用 Object.assign:

        <form>
            <input name="email" type="text" className="form-control" value={registrationInfo.email} onChange={e => setRegistrationInfo(Object.assign(registrationInfo, {email: e.target.value}))} placeholder="Email Address" />
            <input name="phone" type="text" className="form-control" value={registrationInfo.phone} onChange={e => setRegistrationInfo(Object.assign(registrationInfo, {phone: e.target.value}))} placeholder="Phone Number" />
        </form>

最终代码:

import { atom, useSetRecoilState, useRecoilValue } from 'recoil';

const registerAtom = atom({
    key: 'register',
    default: {
        email: '',
        phone: ''
    },
});

function Registration() {

    const setRegistrationInfo = useSetRecoilState(registerAtom);
    const registrationInfo = useRecoilValue(registerAtom);

    return (
        <>
            <form>
                <input name="email" type="text" className="form-control" value={registrationInfo.email} onChange={e => setRegistrationInfo(Object.assign(registrationInfo, {email: e.target.value}))} placeholder="Email Address" />
                <input name="phone" type="text" className="form-control" value={registrationInfo.phone} onChange={e => setRegistrationInfo(Object.assign(registrationInfo, {phone: e.target.value}))} placeholder="Phone Number" />
            </form>
        </>
    )
}

如果您确定您永远不需要阅读或编写电子邮件并且 phone 状态独立,一个简单的方法是使用带有对象值的单个原子(这等同于使用 React 的 useState 挂钩对象值):

import {atom} from 'recoil';

const contactInfoState = atom({
  key: 'contactInfo',
  default: {
    email: '',
    phone: '',
  },
});

然后,像这样使用(每次更新整个对象):

import {useRecoilState} from 'recoil';

function Registration () {
  const [{email, phone}, setContactInfo] = useRecoilState(contactInfoState);
  
  return (
    <form>
      <input
        type="text"
        value={email}
        onChange={ev => setContactInfo({email: ev.target.value, phone})}
        placeholder="Email Address"
      />
      <input
        type="text"
        value={phone}
        onChange={ev => setContactInfo({email, phone: ev.target.value})}
        placeholder="Phone Number"
      />
    </form>
  )
}

然而,执行此操作的惯用方法(以及 Recoil 变得更强大的地方)是 atoms using a selector 的组合,它可以提供一种将值一起读取和写入的方法(就像上面的示例一样) ,但仍然允许使用它们的原子独立读写它们:

import {atom, DefaultValue, selector} from 'recoil';

const emailState = atom({
  key: 'email',
  default: '',
});

const phoneState = atom({
  key: 'phone',
  default: '',
});

const contactInfoState = selector({
  key: 'contactInfo',
  get: ({get}) => {
    // get values from individual atoms:
    const email = get(emailState);
    const phone = get(phoneState);
    // then combine into desired shape (object) and return:
    return {email, phone};
  },
  set: ({set}, value) => {
    // in a Reset action, the value will be DefaultValue (read more in selector docs):
    if (value instanceof DefaultValue) {
      set(emailState, value);
      set(phoneState, value);
      return;
    }
    // otherwise, update individual atoms from new object state:
    set(emailState, value.email);
    set(phoneState, value.phone);
  },
});

这是一个完整且独立的代码片段示例,您可以在此页面上 运行 验证它是否有效:

Note: It uses the UMD versions of React, ReactDOM, and Recoil, so they are exposed globally using those names instead of using import statements.

<script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/recoil@0.5.2/umd/recoil.min.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.16.3/babel.min.js"></script>

<div id="root"></div>

<script type="text/babel" data-type="module" data-presets="react">

const {
  atom,
  DefaultValue,
  RecoilRoot,
  selector,
  useRecoilValue,
  useSetRecoilState,
} = Recoil;

const emailState = atom({
  key: 'email',
  default: '',
});

const phoneState = atom({
  key: 'phone',
  default: '',
});

const contactInfoState = selector({
  key: 'contactInfo',
  get: ({get}) => {
    const email = get(emailState);
    const phone = get(phoneState);
    return {email, phone};
  },
  set: ({set}, value) => {
    if (value instanceof DefaultValue) {
      set(emailState, value);
      set(phoneState, value);
      return;
    }
    set(emailState, value.email);
    set(phoneState, value.phone);
  },
});

function Registration () {
  const {email, phone} = useRecoilValue(contactInfoState);
  const setEmail = useSetRecoilState(emailState);
  const setPhone = useSetRecoilState(phoneState);
  
  return (
    <form>
      <input
        type="text"
        value={email}
        onChange={ev => setEmail(ev.target.value)}
        placeholder="Email Address"
      />
      <input
        type="text"
        value={phone}
        onChange={ev => setPhone(ev.target.value)}
        placeholder="Phone Number"
      />
    </form>
  )
}

function DisplayState () {
  const email = useRecoilValue(emailState);
  const phone = useRecoilValue(phoneState);
  return (
    <pre>
      <code>{JSON.stringify({email, phone}, null, 2)}</code>
    </pre>
  );
}

function Example () {
  return (
    <RecoilRoot>
      <Registration />
      <DisplayState />
    </RecoilRoot>
  );
}

ReactDOM.render(<Example />, document.getElementById('root'));

</script>