question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Feature request] add a way to use reducers inside selectors: useRecoilSelectorWithReducer and stateful selectors

See original GitHub issue

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.

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

  1. 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:closed
  • Created 3 years ago
  • Reactions:6
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

6reactions
drarmstrcommented, May 15, 2020

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!

3reactions
drarmstrcommented, May 16, 2020

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));
Read more comments on GitHub >

github_iconTop Results From Across the Web

Deriving Data with Selectors - Redux
It's not typically possible to use selectors inside of reducers, because a slice reducer only has access to its own slice of the...
Read more >
How to use Reselect selectors inside a Redux reducer
Let's say I want to access a specific item within a specific todo (ex: getCurrentTodo, getTaskListInCurrentTodo). In this case, using your ...
Read more >
The Anatomy Of A React & Redux Module (Applying The ...
In this series we are looking at code organization in the context of a React and Redux application. The takeaways for the “Three...
Read more >
Better State Management with Redux Selectors - Buddy.Works
State management and Redux can be quite a challenge in React apps. Thankfully, selectors can make the entire process a lot more manageable....
Read more >
NgRx + Loading Indicator - Brian F Love
In our reducer() function we toggle the value. We then create a selector for the loading property in the src/app/state/powers/reducers/index.ts ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found