[Feature request] add a way to use reducers inside selectors: useRecoilSelectorWithReducer and stateful selectors
See original GitHub issueSo 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.
- So, the base entity or the atom in enterprise apps would be form management, and I find that forms are segmented into two types: inline forms and composed forms.
- Inline forms are the ones only with one level deep: direct properties inside the main object. For this type of forms, they are trivial to manage. The following example manages them pretty good:
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>
);
}
- Composed forms where the main object has multiple levels of nested objects, ie:
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:useRecoilSelectorWithReducer
function 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)
- Is there any way to include stateful selectors? ie: selectors that accepts and an argument and returns a subscribed sub-state ? for example, If we take the todo list example from the docs, when updating a todo, all todos keep being re-rendered because they are subscribed to the global todo list, One solution is to create an atom per todo, but what If I want to send the whole list to my api ? I did not find any way to select atoms by regex or pattern. I came up with following hack, but again, there are memoization issue regarding the setter:
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
Issue Analytics
- State:
- Created 3 years ago
- Reactions:6
- Comments:5 (2 by maintainers)
Top GitHub Comments
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:
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.
atomFamily
andselectorFamily
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!
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: