Recoil is introduced

Recoil is Facebook’s React data flow management solution, which uses a decentralized approach to managing atomic states.

For students who see the conclusion directly, the full Demo address

The use of Recoil

atom

An Atom represents a state. Atom can be read and written from any component. The component that reads the Atom value implicitly subscribs to the Atom, so any updates to the Atom will cause the component to be rerendered using the corresponding Atom

import { atom } from "recoil";

const rootState = atom({
    key: "rootState".default: "rootState"
});
Copy the code

Use Atom in components

import { useRecoilState } from "useRecoilState";

function Root() {
    const [root, change] = useRecoilState(rootState);
    return (
        <>
            <h3>Root State: {root}</h3>
            <br />
            <button onClick={()= > change(Math.random().toString())}>
                change root
            </button>
        </>
    )}
    </div>
  );
}
Copy the code

RecoilRoot

If you use Recoil within a component, you need to use a RecoilRoot wrapper on its parent component

import { RecoilRoot } from "recoil";

const App = () = > {
    return (
        <RecoilRoot>
            <Root />
        </RecoilRoot>)}Copy the code

One advantage of RecoilRoot is that when the RecoilRoot component is uninstalled, all the internal Atom states are wiped away, and switching components in a project can reset data nicely without additional action.

In addition, RecoilRoot can coexist with multiple users and can be nested, but their data is isolated from each other. Each Atom can be independent of each other.

import { RecoilRoot, useRecoilState } from "recoil";

const App = () = > {
    const [root, change] = useRecoilState(rootState);
    return (
        <>
            <h3>Root State: {root}</h3>// Changes in rootState do not affect the state of Root<button onClick={()= > change(Math.random().toString())}>
                change root
            </button>
            <RecoilRoot>
                <Root />
            </RecoilRoot>
        </>)}Copy the code

However, in real development work, there will be less desirable fragmentation of state management. There will always be shared states. : Boolean, if override: false is set for RecoilRoot, the current RecoilRoot will merge with the nearest RecoilRoot.

import { RecoilRoot, useRecoilState } from "recoil";

const App = () = > {
    const [root, change] = useRecoilState(rootState);
    return (
        <>
            <h3>Root State: {root}</h3>// Here rootState shares the value with the state in Root<button onClick={()= > change(Math.random().toString())}>
                change root
            </button>
            <RecoilRoot override={false}>
                <Root />
            </RecoilRoot>
        </>)}Copy the code

This way, however, all the Atom used in the nested RecoilRoot will be shared with the upper level, losing the scoping function. Even if the child component is unloaded, the internal Atom state will be cached until the upper level RecoilRoot is unloaded.

Is there a way to share state across RecoilRoot while also enjoying the convenience of uninstalling components to clean up data?

Cross-recoilroot data sharing practices

After checking the issue and StackOverflow of the official Github warehouse, I did not find any scenario similar to mine. I tried some ways to clear data but failed. Finally, when I looked through Recoil’s official documentation, I found that Atom Effects and a third-party event library could solve this problem as follows:

import { atom } from "recoil";
import { v4 as uuidv4 } from "uuid";
import { BehaviorSubject } from "rxjs";


export const crossAtom = (key, defaultVal = "") = > {
    const myObservable = new BehaviorSubject({ val: defaultVal });
    const reset = () = > {
        myObservable.next({ val: defaultVal });
    };

    const atomState = atom({
        key: key,
        default: defaultVal,
        effects_UNSTABLE: [
            ({ onSet, setSelf, resetSelf }) = > {
                const uuid = uuidv4();

                const selfObservable = myObservable.subscribe({
                    next: ({ val, uuid: uid }) = > {
                        if (uuid !== uid) setSelf(val);
                    }
                });

                onSet((newVal, oldVal) = > {
                    if (newVal === oldVal) return;
                    myObservable.next({ val: newVal, uuid });
                });

                return () = >{ selfObservable.unsubscribe(); resetSelf(); }; }}]);return {
        atomState,
        reset
    };
};
Copy the code

The final effect achieved:

Demo has 3 layers of RecoilRoot

  • GlobalState Indicates the global share status. When subcomponents are uninstalled, they will not be reset. Route switching will be reset
  • RootState Local state that is reset when the component is uninstalled
  • ChildState Local state that is reset when the component is uninstalled

The implementation principle is that when each Atom is used within the component, a new instance will be generated. The same Atom will subscribe to the same event source, and publish the change event to notify other atom changes when the onSet event is triggered. So generate a unique UUID for each Atom effect to differentiate, and this demo uses RXJS to handle event subscription publishing, theoretically any alternative.

Full Demo address