Recoil: Updating atoms in an atomFamily without useRecoilState

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

So I have a websocket app with a use case that I'm having a hard time wrapping my head around on how recoil would work with it.

I have a collection of records keyed by an ID. Normally, I'd just be able to make a selectorFactory to use and update the state of these records wherever in the view they're needed, but I need to update the state somewhere high in the parent, to initialize these values. I also don't think this can be done in the RecoilRoot initialState callback. The component initializing the values is further down _inside_ the root, not above, so using that callback would be somewhat awkward, I think?

Here's what I need to do, basically:

// in another file
const recordAtomFamily = atomFamily({
  id: 'record',
  default: param => ({ name: param }),
})

// component body
const root = useRecoilRoot()

// in an effect that makes a socket
socket.onmessage = ({ data }) => {
  const command = parseMessage(data)
  if (command.type === 'init') {
    for (const record of command.records) {
      root.set(recordAtomFamily(record.name), record)
    }
  }
}

Most helpful comment

Completely forgot about useRecoilCallback. What a gem.

const recordAtomFamily = atomFamily({
  id: "record",
  default: (param) => ({ name: param }),
});

function Socket() {
  const updateRecord = useRecoilCallback(({ set }, record) => {
    set(recordAtomFamily(record.name), record);
  });

  useEffect(() => {
    socket.onmessage = ({ data }) => {
      const command = parseMessage(data);
      if (command.type === "init") {
        for (const record of command.records) {
          updateRecord(record);
        }
      }
    };
  }, [updateRecord]);

  return null;
}

All 4 comments

Hi @kingdaro. Right now the Atoms&Selectors can only be updated via hooks. Similar issue here: #73.

In the future it _might_ be possible for the websocket to be updating one atom. Then have multiple selectors read from that one atom but only trigger an update if _their_ computed value changes.

Internally, we're working on a mechanism to limit updates when the values have not changed. But, we're planning to really clean up that API before publishing it.
https://github.com/facebookexperimental/Recoil/issues/41#issuecomment-629601674

@kingdaro - Have you considered using useRecoilCallback() for this case? Could you make a callback using that which can set the provided recordAtomFamily atom and then use that callback from your socket effect?

Completely forgot about useRecoilCallback. What a gem.

const recordAtomFamily = atomFamily({
  id: "record",
  default: (param) => ({ name: param }),
});

function Socket() {
  const updateRecord = useRecoilCallback(({ set }, record) => {
    set(recordAtomFamily(record.name), record);
  });

  useEffect(() => {
    socket.onmessage = ({ data }) => {
      const command = parseMessage(data);
      if (command.type === "init") {
        for (const record of command.records) {
          updateRecord(record);
        }
      }
    };
  }, [updateRecord]);

  return null;
}

I didn't even realize that that's what useRecoilCallback was for 馃槄 Though this usage wasn't documented (at time of writing); I had to look at the source code after seeing the replies to this issue. Either way, I think I can consider this closed

Was this page helpful?
0 / 5 - 0 ratings