So I was playing around with recoil and how it can be used in real world/enterprise apps. I had so many AHAs to be honest. I highly congratulate you for the hard work that was done.
I just started digging in the source code, so I am not qualified for contributing to solve bugs and/or add new features.
const MY_FORM_ATOM = atom({
key: 'myForm',
default: { username: '', password: '', dateOfBirth: '', rememberMe: true },
});
const MY_FORM = selector({
key: 'myFormManagement',
get: ({ get }) => get(MY_FORM_ATOM),
set: ({ get, set }, { target: { name, value, type, checked } }) => {
set(MY_FORM_ATOM, { ...get(MY_FORM_ATOM), [name]: type === 'checkbox' ? checked : value });
},
});
function MyFormComponent() {
const [values, updater] = useRecoilState(MY_FORM);
return (
<div>
<input name="username" value={values.username} onChange={updater} />
{/* And so on for the other fields */}
</div>
);
}
User { username, fullName, age, addresses: [{city, country, zipCode, description}], todos: [{id, description, completed}]. The previous solution will be unfortunate since the updater will have to preserve the whole state while updating a small portion of it and the code will look very creepy. So, I came out with this: useRecoilSelectorWithReducerfunction useRecoilSelectorWithReducer({ selectorKey, selectorAtom, reducer }) {
return useRecoilState(
selector({
key: selectorKey,
get: ({ get }) => get(selectorAtom),
set: ({ get, set }, action) => {
set(selectorAtom, reducer(get(selectorAtom), action));
},
}),
);
}
This allows us to benefit from reducers and their power and do like these things:
function inlineUpdater(e) {
const { name, value, type, checked } = e.target;
updateValue({ type: 'change_value', payload: { name, value, type, checked } });
}
function changeAddressField(id, event) {
const { name, value } = event.target;
updateValue({ type: 'change_address', payload: {id, name, value } });
}
function addAddress() {
updateValue({ type: 'add_address', payload: {id, description, country, city } });
}
function removeAddress(id) {
updateValue({ type: 'remove_address', payload: id });
}
So my request here, Is there any way that you can adopt/design something similar out of the box ? I think the usage of a reducer to update a state will have a major power in managing complex states.
The current implementation I did, changes the value of the setter at every render, and I don't feel ok about it. (May be I have to think more about a way to do it)
export const TODO_BY_ID = (id) =>
selector({
key: 'getTodoById',
get: ({ get }) => get(TODOS_ATOM)[id],
set: ({ get, set }, value) => {
set(TODOS_ATOM, { ...get(TODOS_ATOM), [value.id]: { ...value} });
},
});
I understand that you may be overwhelmed by the huge amount of issues and requests, but for me, this is a sign of success. So I did my best to group my thoughts in this issue.
Thank you again for the great library
I solved the memoization issue I had with my useRecoilSelectorWithReducer.
I was passing a new created selector each time to useRecoilState. So the new version looks like this:
export function useRecoilSelectorWithReducer({ selectorKey, selectorAtom, reducer }) {
const memoizedSelector = React.useMemo(
() =>
selector({
key: selectorKey,
get: ({ get }) => get(selectorAtom),
set: ({ get, set }, action) => {
set(selectorAtom, reducer(get(selectorAtom), action));
},
}),
[selectorKey, selectorAtom, reducer],
);
return useRecoilState(memoizedSelector);
}
Part of what is nice about Recoil is that you can build more complex helpers on top of the simple atom and selector building blocks. You may be interested in the selectorFamily helper. This pattern wraps a selector in a function and basically allows you to pass parameters to the get/set functions.
For example:
const mySelectorFamily = selectorFamily({
key: 'MySelectorFamily',
get: params => ({get}) => { get stuff based on params },
set: params => ({set, get}) => { set stuff based on params }<
});
Then use it like:
const [value, setValue] = useRecoilState(mySelectorFamily({foo}));
One thing nice about this approach is that exposing it as a function which returns a selector means we can then compose and use it by other selectors, which you can't do with wrapping it with a hook. atomFamily and selectorFamily are some of the available helpers in the RecoilUtils.js module. I think we have a PR in the works to properly export that in the build.
But, as you can see, you're also free to make cool helpers or abstractions with hooks like you demonstrate here. Thanks for taking such a close look at Recoil!
Pretty clear for me now, I didn't knew about the atomFamily stuff.
Is there any plans to add a comparator to selectors ? ie: prevent re-renders if the comparator decides so. This will be very handy, for example if I want to select a substate, and not re-render until a value from it changes, but do not re-render if something in the whole atom changes.
For people looking for a reducer example without using hooks, here it is
export function reducerSelector({ selectorKey, selectorAtom, reducer }) {
return selector({
key: selectorKey,
get: ({ get }) => get(selectorAtom),
set: ({ get, set }, action) => {
set(selectorAtom, reducer(get(selectorAtom), action));
},
});
}
usage:
const myReducerSelector = reducerSelector({
selectorKey: 'myFormManager',
reducer: myFormReducer,
selectorAtom: MY_FORM_ATOM,
});
Yes, actually. We're looking at a few options for the comparator concept.
For your reducer example, also note that we have an updater form of the setter:
set(selectorAtom, previousValue => reducer(previousValue, action));
Thanks for the hint.
Most helpful comment
Part of what is nice about Recoil is that you can build more complex helpers on top of the simple atom and selector building blocks. You may be interested in the
selectorFamilyhelper. This pattern wraps a selector in a function and basically allows you to pass parameters to the get/set functions.For example:
Then use it like:
One thing nice about this approach is that exposing it as a function which returns a selector means we can then compose and use it by other selectors, which you can't do with wrapping it with a hook.
atomFamilyandselectorFamilyare some of the available helpers in the RecoilUtils.js module. I think we have a PR in the works to properly export that in the build.But, as you can see, you're also free to make cool helpers or abstractions with hooks like you demonstrate here. Thanks for taking such a close look at Recoil!