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