Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get all elements from an atomFamily in recoil?

Im playing around with recoil for the first time and cant figure out how I can read all elements from an atomFamily. Let's say I have an app where a user can add meals:

export const meals = atomFamily({
  key: "meals",
  default: {}
});

And I can initialize a meal as follows:

const [meal, setMeal] = useRecoilState(meals("bananas"));
const bananas = setMeal({name: "bananas", price: 5});

How can I read all items which have been added to this atomFamily?

like image 738
DannyMoshe Avatar asked Oct 19 '20 19:10

DannyMoshe


People also ask

What is atomFamily in recoil?

But, Recoil provides this pattern for you with the atomFamily() utility. An Atom Family represents a collection of atoms. When you call atomFamily() it will return a function which provides the RecoilState atom based on the parameters you pass in.

What is a recoil atom react?

Recoil lets you create a data-flow graph that flows from atoms (shared state) through selectors (pure functions) and down into your React components. Atoms are units of state that components can subscribe to. Selectors transform this state either synchronously or asynchronously.

What is recoil selector?

Selectors represent a function, or derived state in Recoil. You can think of them as similar to an "idempotent" or "pure function" without side-effects that always returns the same value for a given set of dependency values.

How to create an atom using recoil?

This is how we can create an atom using Recoil: To create an atom we need to provide a key, which should be a unique value. This key is used for persistence, debugging, etc. Also, we need to provide the default value of our atom, it can be anything such as arrays, objects, strings, functions, etc.

Can atoms be used to store promise's or recoilvalue's?

Atoms cannot be used to store Promise's or RecoilValue's directly, but they may be wrapped in an object. Note that Promise's may be mutable. Atoms can be set to a function, as long as it is pure, but to do so you may need to use the updater form of setters. (e.g. set (myAtom, () => myFunc);).

How to get all members of an atomfamily?

You have to track all ids of the atomFamily to get all members of the family. Keep in mind that this is not really a list, more like a map. Something like this should get you going. // atomFamily const meals = atomFamily ( { key: "meals", default: {} }); const mealIds = atom ( { key: "mealsIds", default: [] });

Why does recoil freeze objects stored in atoms?

If an object stored in an atom was mutated directly it may bypass this and cause state changes without properly notifying subscribing components. To help detect bugs Recoil will freeze objects stored in atoms in development mode. Most often, you'll use the following hooks to interact with atoms:


Video Answer


2 Answers

You have to track all ids of the atomFamily to get all members of the family. Keep in mind that this is not really a list, more like a map.

Something like this should get you going.

// atomFamily
const meals = atomFamily({
  key: "meals",
  default: {}
});

const mealIds = atom({
  key: "mealsIds",
  default: []
});

When creating a new objects inside the family you also have to update the mealIds atom.

I usually use a useRecoilCallback hook to sync this together

  const createMeal = useRecoilCallback(({ set }) => (mealId, price) => {
    set(mealIds, currVal => [...currVal, mealId]);
    set(meals(mealId), {name: mealId, price});
  }, []);

This way you can create a meal by calling:

createMeal("bananas", 5);

And get all ids via:

const ids = useRecoilValue(mealIds);
like image 143
Johannes Klauß Avatar answered Oct 19 '22 13:10

Johannes Klauß


Instead of using useRecoilCallback you can abstract it with selectorFamily.

// atomFamily
const mealsAtom = atomFamily({
  key: "meals",
  default: {}
});

const mealIds = atom({
  key: "mealsIds",
  default: []
});

// abstraction
const meals = selectorFamily({
  key: "meals-access",
  get:  (id) => ({ get }) => {
      const atom = get(mealsAtom(id));
      return atom;
  },
  set: (id) => ({set}, meal) => {
      set(mealsAtom(id), meal);
      set(mealIds (id), prev => [...prev, meal.id)]);
  }
});

Further more, in case you would like to support reset you can use the following code:

// atomFamily
const mealsAtom = atomFamily({
  key: "meals",
  default: {}
});

const mealIds = atom({
  key: "mealsIds",
  default: []
});

// abstraction
const meals = selectorFamily({
  key: "meals-access",
  get:  (id) => ({ get }) => {
      const atom = get(mealsAtom(id));
      return atom;
  },
  set: (id) => ({set, reset}, meal) => {
      if(meal instanceof DefaultValue) {
        // DefaultValue means reset context
        reset(mealsAtom(id));
        reset(mealIds (id));
        return;
      }
      set(mealsAtom(id), meal);
      set(mealIds (id), prev => [...prev, meal.id)]);
  }
});

If you're using Typescript you can make it more elegant by using the following guard.

import { DefaultValue } from 'recoil';

export const guardRecoilDefaultValue = (
  candidate: unknown
): candidate is DefaultValue => {
  if (candidate instanceof DefaultValue) return true;
  return false;
};

Using this guard with Typescript will look something like:

// atomFamily
const mealsAtom = atomFamily<IMeal, number>({
  key: "meals",
  default: {}
});

const mealIds = atom<number[]>({
  key: "mealsIds",
  default: []
});

// abstraction
const meals = selectorFamily<IMeal, number>({
  key: "meals-access",
  get:  (id) => ({ get }) => {
      const atom = get(mealsAtom(id));
      return atom;
  },
  set: (id) => ({set, reset}, meal) => {
      if (guardRecoilDefaultValue(meal)) {
        // DefaultValue means reset context
        reset(mealsAtom(id));
        reset(mealIds (id));
        return;
      }
      // from this line you got IMeal (not IMeal | DefaultValue)
      set(mealsAtom(id), meal);
      set(mealIds (id), prev => [...prev, meal.id)]);
  }
});
like image 15
Bnaya Avatar answered Oct 19 '22 12:10

Bnaya