If you are certain that you will never need to read or write the email and phone states independently, a simple approach is to use a single atom with an object value (this is equivalent to using React's useState hook with an object value):
import {atom} from 'recoil';
const contactInfoState = atom({
key: 'contactInfo',
default: {
email: '',
phone: '',
},
});
Then, use like this (updating the entire object every time):
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>
)
}
However, the idiomatic way to do this (and where Recoil becomes more powerful) is composition of atoms using a selector, which can provide a way for reading and writing the values together (just like in the example above), yet still allows for reading and writing them independently using their 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);
},
});
Here's a complete and self-contained example in a snippet, which you can run on this page to verify that it works:
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>