【问题标题】:Multiple form values in one react recoil atom override each other一个反应反冲原子中的多个表单值相互覆盖
【发布时间】:2021-12-24 19:34:48
【问题描述】:

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

我有一个包含 2 个字段的注册表单;电子邮件和电话。

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

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

【问题讨论】:

    标签: reactjs react-hooks recoiljs


    【解决方案1】:

    如果你确定你永远不需要独立读取或写入电子邮件和电话状态,一个简单的方法是使用带有对象值的单个原子(这相当于使用 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 变得更强大的地方)是使用 selector 组合 atoms,它可以提供一起读取和写入值的方法(就像在示例中一样以上),但仍然允许使用它们的原子独立地读取和写入它们:

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

    这是一个完整且自包含的 sn-p 示例,您可以在此页面上运行它来验证它是否有效:

    注意:它使用 ReactReactDOMRecoil 的 UMD 版本,因此使用这些名称而不是使用 import 语句来全局公开它们。

    <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>

    【讨论】:

    • 哦,这真的很好用,我不知道选择器..基本上是 PHP 之类的 getter 和 setter ......非常感谢
    • 是否可以只查看其他组件的值?
    • @CodeSauce 如果通过“查看”,您的意思是“读取/访问”,那么是的,这在 sn-p 中的 DisplayState 组件中通过 useRecoilValue 钩子进行了演示。
    • 哦,我的错,这是一种享受......再次感谢,非常感谢
    【解决方案2】:

    您的 atom 有一个值 register 在开始时保存一个数组,然后分配输入的值。

    inputs 都设置原子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>
            </>
        )
    }
    

    【讨论】:

    • 在本例中使用setRegistrationInfo(Object.assign(registrationInfo, ...)) 正在改变(只读)Recoil 状态。这是生产中的错误,并在开发中引发异常。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-27
    • 1970-01-01
    • 2017-02-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多