facebookexperimental / Recoil

Recoil is an experimental state management library for React apps. It provides several capabilities that are difficult to achieve with React alone, while being compatible with the newest features of React.
https://recoiljs.org/
MIT License
19.6k stars 1.19k forks source link

Can an atom's value be changed from a non-react environment? #546

Closed Nishchit14 closed 3 years ago

Nishchit14 commented 4 years ago

Recently I came across the use case like a pure js file Realtime.service.js is making a connection with the WebSocket server and streaming realtime data to UI (need to feed multiple React's components with a chunk of data coming from realtime WS).

Now if Recoil's atom state can be changed directly from Realtime.service.js (which is not a React component but js service) with realtime steam then all the components connected with the relative atoms will be updated on realtime data streaming.

Is that a valid use case? Right now I've seen that Atom's state only get/set default-value by React's hook.

In redux it is possible, we can update the store from any js file (non-react env). and the react-redux bridge auto handles the changes to connected components. Now we're moving from Redux to Recoil, we're having this roadblock.

kulkalkul commented 4 years ago

It isn't possible to access them outside of React as stated in #5. But you can do something like this to sync them: https://recoiljs.org/docs/guides/asynchronous-state-sync

I use this method to sync my state to leveldown DB in my electron application.

ajoslin commented 4 years ago

You could hack it

function MyCmp () {
  const setValue = window.setValue = useSetRecoilState(someState)
}

function somewhereElse () {
  window.setValue(123)
}
BenjaBobs commented 4 years ago

My team uses an empty component to capture and expose a getLoadable() and set() function. This only works if you have exactly one <RecoilRoot> though:

import React from 'react';
import {
    atom, AtomOptions, Loadable, RecoilState, RecoilValue, selector, Snapshot, useRecoilCallback,
    useRecoilSnapshot
} from 'recoil';

// internal variables to hold the relevant recoil objects
let __snapshot: Snapshot = null as any;
let __set: <T>(
  recoilVal: RecoilState<T>,
  valOrUpdater: ((currVal: T) => T) | T
) => void = null as any;

// expose a function to set recoil state
export function RecoilSet<T>(
  recoilVal: RecoilState<T>,
  valOrUpdater: ((currVal: T) => T) | T
) {
  __set(recoilVal, valOrUpdater);
}

// expose a function to get recoil state
export function RecoilGetLoadable<T>(recoilValue: RecoilValue<T>): Loadable<T> {
  return __snapshot.getLoadable(recoilValue);
}

// This component captures a snapshot to expose a get function, and a callback to expose a set function
export function RecoilUtilsComponent() {
  __snapshot = useRecoilSnapshot();

  useRecoilCallback(({ set }) => {
    __set = set;

    return async () => {};
  })();

  return <></>;
}

And this is our react root:

export default function Startup() {
  return (
    <RecoilRoot>
      {/* this component is empty and it's only purpose is to sync external state with Recoil */}
      <RecoilUtilsComponent />
      <HistoryRecoilSync />
      <MediaQueryRecoilSync />
      <Initialize>
        <AppHost />
      </Initialize>
    </RecoilRoot>
  );
}

It's handy when we want to set some auth data as part of authentication/login flow.

drarmstr commented 4 years ago

I'm hopeful that this API #380 could help with this use case.

drarmstr commented 3 years ago

Updated documentation for syncing Recoil state with external storage in #680