/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow strict-local * @format * @oncall recoil */ 'use strict'; import type {ValueOrUpdater} from '../recoil_values/Recoil_callbackTypes'; import type {RecoilState, RecoilValue} from './Recoil_RecoilValue'; import type {NodeKey, Store, TreeState} from './Recoil_State'; const {loadableWithValue} = require('../adt/Recoil_Loadable'); const {initializeNode} = require('./Recoil_FunctionalCore'); const {DEFAULT_VALUE, getNode} = require('./Recoil_Node'); const { copyTreeState, getRecoilValueAsLoadable, invalidateDownstreams, writeLoadableToTreeState, } = require('./Recoil_RecoilValueInterface'); const err = require('recoil-shared/util/Recoil_err'); export interface TransactionInterface { get: (RecoilValue) => T; set: (RecoilState, ValueOrUpdater) => void; reset: (RecoilState) => void; } function isAtom(recoilValue: RecoilValue): boolean { return getNode(recoilValue.key).nodeType === 'atom'; } class TransactionInterfaceImpl { _store: Store; _treeState: TreeState; _changes: Map; constructor(store: Store, treeState: TreeState) { this._store = store; this._treeState = treeState; this._changes = new Map(); } // Allow destructing // eslint-disable-next-line fb-www/extra-arrow-initializer get = (recoilValue: RecoilValue): T => { if (this._changes.has(recoilValue.key)) { // $FlowIssue[incompatible-return] return this._changes.get(recoilValue.key); } if (!isAtom(recoilValue)) { throw err('Reading selectors within atomicUpdate is not supported'); } const loadable = getRecoilValueAsLoadable( this._store, recoilValue, this._treeState, ); if (loadable.state === 'hasValue') { return loadable.contents; } else if (loadable.state === 'hasError') { throw loadable.contents; } else { throw err( `Expected Recoil atom ${recoilValue.key} to have a value, but it is in a loading state.`, ); } }; // Allow destructing // eslint-disable-next-line fb-www/extra-arrow-initializer set = ( recoilState: RecoilState, valueOrUpdater: ValueOrUpdater, ): void => { if (!isAtom(recoilState)) { throw err('Setting selectors within atomicUpdate is not supported'); } if (typeof valueOrUpdater === 'function') { const current = this.get(recoilState); this._changes.set(recoilState.key, (valueOrUpdater: any)(current)); // flowlint-line unclear-type:off } else { // Initialize atom and run effects if not initialized yet initializeNode(this._store, recoilState.key, 'set'); this._changes.set(recoilState.key, valueOrUpdater); } }; // Allow destructing // eslint-disable-next-line fb-www/extra-arrow-initializer reset = (recoilState: RecoilState): void => { this.set(recoilState, DEFAULT_VALUE); }; newTreeState_INTERNAL(): TreeState { if (this._changes.size === 0) { return this._treeState; } const newState = copyTreeState(this._treeState); for (const [k, v] of this._changes) { writeLoadableToTreeState(newState, k, loadableWithValue(v)); } invalidateDownstreams(this._store, newState); return newState; } } function atomicUpdater(store: Store): ((TransactionInterface) => void) => void { return fn => { store.replaceState(treeState => { const changeset = new TransactionInterfaceImpl(store, treeState); fn(changeset); return changeset.newTreeState_INTERNAL(); }); }; } module.exports = {atomicUpdater};