Recoil: Is it possible to set the initial state (or overwrite all state) of an entire atom family?

Created on 22 Sep 2020  路  4Comments  路  Source: facebookexperimental/Recoil

My use case is something like a form (working on a library for this, actually). Each field in the form is represented by an atom. So I want to use something like this for each field:

useRecoilState(fieldStateFamily(fieldId)) // where fieldId would be e.g. firstName, lastName, email, etc.

However, I need a way to set the initial state of all the fields from an object, for example:

{ firstName: 'Jane', lastName: 'Doe', color: 'blue' }

I wish there was a hook, so I could do something like this:

const setFieldFamilyState = useSetFamilyState(fieldStateFamily)
useEffect(() => setFieldFamilyState(initialValuesObject), [])

Does this make sense... would something like this be possible? It's not clear to me how you would solve this use case with Recoil. Any insights appreciated.

question

Most helpful comment

@vonwao - The default option for atomFamily() could be a single value that is used for each individual atom. Or, it could be a function that is provided the parameter value and returns the default value for that particular member of the family. So, you could use this to have the appropriate default for each entry in the form. Additionally, for more advanced use cases you could set the default to a selectorFamily() if you needed to query or something for the default.

Simple default for all atoms:

const formState = atomFamily({
  key: 'My Form State',
  default: null,
});

Each atom has it's own default:

const initialValuesObject = { firstName: 'Jane', lastName: 'Doe', color: 'blue' };
const formState = atomFamily({
  key: 'My Form State',
  default: field => initialValuesObject[field],
});

Dynamic default for each field:

const formState = atomFamily({
  key: 'My Form State',
  default: selectorFamily({
    key: 'My Form State / Default',
    get: field => () => fetch default for field...,
  }),
});

All 4 comments

Looks like current atomFamily already cover your use-case. You can reference https://recoiljs.org/docs/api-reference/utils/atomFamily#example to set the default in the atomFamily.

Looks like current atomFamily already cover your use-case. You can reference https://recoiljs.org/docs/api-reference/utils/atomFamily#example to set the default in the atomFamily.

@ShinyChang I don't think so, default looks to be the default value for each atom in the family.

Another problem is that, even if you could set a list of atoms with default, you still have to know the value at the time you declare the atomFamily (outside of react component). In my case, I can't know it. I need to be able to set it dynamically.

You can see the demo which fit your requirements.

const createFieldAtom = (key, defaultValue) =>
  atom({ key, default: defaultValue });

const createFieldsAtom = (scope, defalutValue) => {
  const fieldAtoms = Object.entries(defalutValue).reduce(
    (acc, [key, defaultValue]) => {
      const atom = createFieldAtom(`${scope}${key}`, defaultValue);
      acc[key] = atom;
      return acc;
    },
    {}
  );
  const fieldsAtom = selector({
    key: `${scope}Selector`,
    get: ({ get }) => {
      return Object.entries(fieldAtoms).reduce((acc, [key, atom]) => {
        acc[key] = get(atom);
        return acc;
      }, {});
    },
    set: ({ set }, newValue) => {
      return Object.entries(newValue).forEach(([key, value]) => {
        set(fieldAtoms[key], newValue[key]);
      });
    }
  });
  return fieldsAtom;
};

const profileAtom = createFieldsAtom("profile", {
  firstName: "Jane",
  lastName: "Doe",
  color: "blue"
});

But this means you will re-render if you updated any value, it's not recommend with Recoil.

Better to defined it separately to have maximum performance.

const createFieldAtom = (key, defaultValue) =>
  atom({ key, default: defaultValue });

const firstNameAtom = createFieldAtom('firstName', 'Jane');
const lastNameAtom = createFieldAtom('lastName', 'Doe');
const colorAtom = createFieldAtom('color', 'blue');

const profileSelector = selector({
  key: 'profileSelector',
  get: ({ get }) => {
    const firstName = get(firstNameAtom);
    const lastName = get(lastNameAtom);
    const color = get(colorAtom);
    return { firstName, lastName, color };
  }
});

@vonwao - The default option for atomFamily() could be a single value that is used for each individual atom. Or, it could be a function that is provided the parameter value and returns the default value for that particular member of the family. So, you could use this to have the appropriate default for each entry in the form. Additionally, for more advanced use cases you could set the default to a selectorFamily() if you needed to query or something for the default.

Simple default for all atoms:

const formState = atomFamily({
  key: 'My Form State',
  default: null,
});

Each atom has it's own default:

const initialValuesObject = { firstName: 'Jane', lastName: 'Doe', color: 'blue' };
const formState = atomFamily({
  key: 'My Form State',
  default: field => initialValuesObject[field],
});

Dynamic default for each field:

const formState = atomFamily({
  key: 'My Form State',
  default: selectorFamily({
    key: 'My Form State / Default',
    get: field => () => fetch default for field...,
  }),
});
Was this page helpful?
0 / 5 - 0 ratings

Related issues

Sawtaytoes picture Sawtaytoes  路  4Comments

yuantongkang picture yuantongkang  路  3Comments

atanasster picture atanasster  路  3Comments

karevn picture karevn  路  3Comments

polemius picture polemius  路  3Comments