function initAtom()

in packages/recoil/recoil_values/Recoil_atom.js [251:471]


  function initAtom(
    store: Store,
    initState: TreeState,
    trigger: Trigger,
  ): () => void {
    liveStoresCount++;
    const cleanupAtom = () => {
      liveStoresCount--;
      cleanupEffectsByStore.get(store)?.forEach(cleanup => cleanup());
      cleanupEffectsByStore.delete(store);
    };

    store.getState().knownAtoms.add(key);

    // Setup async defaults to notify subscribers when they resolve
    if (defaultLoadable.state === 'loading') {
      const notifyDefaultSubscribers = () => {
        const state = store.getState().nextTree ?? store.getState().currentTree;
        if (!state.atomValues.has(key)) {
          markRecoilValueModified(store, node);
        }
      };
      defaultLoadable.contents.finally(notifyDefaultSubscribers);
    }

    ///////////////////
    // Run Atom Effects
    ///////////////////

    const effects = options.effects ?? options.effects_UNSTABLE;
    if (effects != null) {
      // This state is scoped by Store, since this is in the initAtom() closure
      let duringInit = true;
      let initValue: NewValue<T> = DEFAULT_VALUE;
      let isInitError: boolean = false;
      let pendingSetSelf: ?{
        effect: AtomEffect<T>,
        value: T | DefaultValue,
      } = null;

      function getLoadable<S>(recoilValue: RecoilValue<S>): Loadable<S> {
        // Normally we can just get the current value of another atom.
        // But for our own value we need to check if there is a pending
        // initialized value or get the fallback default value.
        if (duringInit && recoilValue.key === key) {
          // Cast T to S
          const retValue: NewValue<S> = (initValue: any); // flowlint-line unclear-type:off
          return retValue instanceof DefaultValue
            ? (peekAtom(store, initState): any) // flowlint-line unclear-type:off
            : isPromise(retValue)
            ? loadableWithPromise(
                retValue.then((v: S | DefaultValue): S | Promise<S> =>
                  v instanceof DefaultValue
                    ? // Cast T to S
                      (defaultLoadable: any).toPromise() // flowlint-line unclear-type:off
                    : v,
                ),
              )
            : loadableWithValue(retValue);
        }
        return getRecoilValueAsLoadable(store, recoilValue);
      }

      function getPromise<S>(recoilValue: RecoilValue<S>): Promise<S> {
        return getLoadable(recoilValue).toPromise();
      }

      function getInfo_UNSTABLE<S>(
        recoilValue: RecoilValue<S>,
      ): RecoilValueInfo<S> {
        const info = peekNodeInfo(
          store,
          store.getState().nextTree ?? store.getState().currentTree,
          recoilValue.key,
        );
        return duringInit &&
          recoilValue.key === key &&
          !(initValue instanceof DefaultValue)
          ? {...info, isSet: true, loadable: getLoadable(recoilValue)}
          : info;
      }

      const setSelf =
        (effect: AtomEffect<T>) => (valueOrUpdater: NewValueOrUpdater<T>) => {
          if (duringInit) {
            const currentLoadable = getLoadable(node);
            const currentValue: T | DefaultValue =
              currentLoadable.state === 'hasValue'
                ? currentLoadable.contents
                : DEFAULT_VALUE;
            initValue =
              typeof valueOrUpdater === 'function'
                ? // cast to any because we can't restrict T from being a function without losing support for opaque types
                  (valueOrUpdater: any)(currentValue) // flowlint-line unclear-type:off
                : valueOrUpdater;
            if (isPromise(initValue)) {
              initValue = initValue.then(value => {
                // Avoid calling onSet() when setSelf() initializes with a Promise
                pendingSetSelf = {effect, value};
                return value;
              });
            }
          } else {
            if (isPromise(valueOrUpdater)) {
              throw err('Setting atoms to async values is not implemented.');
            }

            if (typeof valueOrUpdater !== 'function') {
              pendingSetSelf = {effect, value: valueOrUpdater};
            }

            setRecoilValue(
              store,
              node,
              typeof valueOrUpdater === 'function'
                ? currentValue => {
                    const newValue =
                      // cast to any because we can't restrict T from being a function without losing support for opaque types
                      (valueOrUpdater: any)(currentValue); // flowlint-line unclear-type:off
                    pendingSetSelf = {effect, value: newValue};
                    return newValue;
                  }
                : valueOrUpdater,
            );
          }
        };
      const resetSelf = effect => () => setSelf(effect)(DEFAULT_VALUE);

      const onSet =
        effect => (handler: (T, T | DefaultValue, boolean) => void) => {
          const {release} = store.subscribeToTransactions(currentStore => {
            // eslint-disable-next-line prefer-const
            let {currentTree, previousTree} = currentStore.getState();
            if (!previousTree) {
              recoverableViolation(
                'Transaction subscribers notified without a next tree being present -- this is a bug in Recoil',
                'recoil',
              );
              previousTree = currentTree; // attempt to trundle on
            }
            const newLoadable =
              currentTree.atomValues.get(key) ?? defaultLoadable;
            if (newLoadable.state === 'hasValue') {
              const newValue: T = newLoadable.contents;
              const oldLoadable =
                previousTree.atomValues.get(key) ?? defaultLoadable;
              const oldValue: T | DefaultValue =
                oldLoadable.state === 'hasValue'
                  ? oldLoadable.contents
                  : DEFAULT_VALUE; // TODO This isn't actually valid, use as a placeholder for now.

              // Ignore atom value changes that were set via setSelf() in the same effect.
              // We will still properly call the handler if there was a subsequent
              // set from something other than an atom effect which was batched
              // with the `setSelf()` call.  However, we may incorrectly ignore
              // the handler if the subsequent batched call happens to set the
              // atom to the exact same value as the `setSelf()`.   But, in that
              // case, it was kind of a noop, so the semantics are debatable..
              if (
                pendingSetSelf?.effect !== effect ||
                pendingSetSelf?.value !== newValue
              ) {
                handler(newValue, oldValue, !currentTree.atomValues.has(key));
              } else if (pendingSetSelf?.effect === effect) {
                pendingSetSelf = null;
              }
            }
          }, key);
          cleanupEffectsByStore.set(store, [
            ...(cleanupEffectsByStore.get(store) ?? []),
            release,
          ]);
        };

      for (const effect of effects) {
        try {
          const cleanup = effect({
            node,
            storeID: store.storeID,
            trigger,
            setSelf: setSelf(effect),
            resetSelf: resetSelf(effect),
            onSet: onSet(effect),
            getPromise,
            getLoadable,
            getInfo_UNSTABLE,
          });
          if (cleanup != null) {
            cleanupEffectsByStore.set(store, [
              ...(cleanupEffectsByStore.get(store) ?? []),
              cleanup,
            ]);
          }
        } catch (error) {
          initValue = error;
          isInitError = true;
        }
      }

      duringInit = false;

      // Mutate initial state in place since we know there are no other subscribers
      // since we are the ones initializing on first use.
      if (!(initValue instanceof DefaultValue)) {
        const frozenInitValue = maybeFreezeValueOrPromise(initValue);
        const initLoadable = isInitError
          ? loadableWithError(initValue)
          : isPromise(frozenInitValue)
          ? loadableWithPromise(wrapPendingPromise(store, frozenInitValue))
          : loadableWithValue(frozenInitValue);
        initState.atomValues.set(key, initLoadable);

        // If there is a pending transaction, then also mutate the next state tree.
        // This could happen if the atom was first initialized in an action that
        // also updated some other atom's state.
        store.getState().nextTree?.atomValues.set(key, initLoadable);
      }
    }

    return cleanupAtom;
  }