Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/386
When we set an atom to the same value (based on reference equality), or we have redundant resets, we can avoid updating the state, re-evaluating downstream nodes, or re-rendering subscribing components.
Reviewed By: habond
Differential Revision: D21964833
fbshipit-source-id: 132b6870d35a4f209bb8ecb9e81d59dd3866397e
Summary:
Disable the `react/prop-types` ESLint rule. It is causing CI to fail because of recently added components in tests. I don't think this rule is needed since we can statically type component props with Flow.
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/392
Reviewed By: aaronabramov
Differential Revision: D22218079
Pulled By: drarmstr
fbshipit-source-id: be7980a73c1265e40a28160d11a98b758ceabd7f
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/401
Normalize updating recoil values with `setRecoilValue()` and handle the updater form of new values there instead of callers needing to use `valueFromValueOrUpdater()`
Reviewed By: davidmccabe
Differential Revision: D22224054
fbshipit-source-id: 61afdce501adf370a50bfd6d6ef0ddd332a4d1c6
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/399
Avoid replacing state and initiating a render when initializing atom state with an effect. No need to replace the state since we know there are no other subscribers to an atom if we are the ones initializing it on first use.
Reviewed By: davidmccabe
Differential Revision: D22223025
fbshipit-source-id: 1117be6f1e05fc2843da217ccd47aa29c65e67fb
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/400
Scope Atom lifetime per `<RecoilRoot>` Store instead of TreeState per Snapshot.
Reviewed By: davidmccabe
Differential Revision: D22222735
fbshipit-source-id: f9912e265b1bc60028acb203669e8fec070eec07
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/381
Add a test that going to a snapshot where an atom has not been initialized yet will cause it to be initialized again. If we had cleanup handlers, then the cleanup handler should also be caused when going to the new snapshot.
Reviewed By: csantos42
Differential Revision: D22033254
fbshipit-source-id: 60a9e847a12f891e20b26d5d361ea9faa7d2d724
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/385
Report the old value as well as the new value from an Atom onSet effect. The snapshot provided to this effect is for the new updated state, so it was not otherwise possible to get the old value.
Reviewed By: habond
Differential Revision: D22015656
fbshipit-source-id: 8d2d2c2b1e24cf1a3e598af5904eee68c3c5b1bf
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/384
Add `onSet()` handler for Atom Effects. This effect can be used to subscribe to atom changes for side-effects, such as synching with remote storage.
This handler is called after the React batch after the atom is set, but before the global `useRecoilTransactionObserver()`. There currently isn't a way to cancel the `onSet()` handler, but we could add that if needed.
Example usage:
```
const myAtom = atom({
key: 'MyAtom',
default: undefined,
effects: [({node, setSelf, onSet}) => {
const storage = getStorage(node.key);
// set a value for synchronous init that overrides default
setSelf(storage.get(value));
// Subscribe to storage updates
storage.subscribe(value => setSelf(value));
// Subscribe local changes to update storage
onSet(value =>
value === undefined
? storage.reset()
: storage.set(value)
);
}],
});
```
Reviewed By: csantos42
Differential Revision: D21949700
fbshipit-source-id: b13929648efbc0b5c643cab254d3764445449a76
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/382
Add a `getDeps_UNSTABLE()` accessor to `Snapshot` that takes a `RecoilValue` and returns an Iterable of current dependencies.
```
class Snapshot {
...
getDeps_UNSTABLE: RecoilValue<T> => Iterable<RecoilValue<mixed>>,
};
```
Reviewed By: csantos42, maxijb
Differential Revision: D21926731
fbshipit-source-id: 318a7da152611f1137a621d6bfc65303bf64ec68
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/383
Add `getNodes` accessor to the `Snapshot` interface
Provide the following interface to allow us to query sets of nodes based on our usage:
```
class Snapshot {
...
getNodes_UNSTABLE: ({
types?: Array<'atom' | 'selector'>,
dirty?: boolean,
}) => Iterable<RecoilValue<mixed>>;
}
```
* **`types`** - An array specifying the types of nodes to return including `atom` and `selector`. The default is both.
* **`dirty`** - Only return dirty nodes. Currently this only supports dirty atoms, but we should probably support dirty selectors for debug tools.
To keep scalability in mind, this interface returns an Iterable using a generator function so that we never produces temporary arrays containing all nodes.
NOTE: Feedback on the API for query parameters/options is welcome... I'm not super keen on the current approach, which by default reports initialized nodes. The set of initialized nodes in a snapshot my change as selectors are evaluated and discover more. However, even if we report all globally registered nodes it's really the same problem as nodes can still be dynamically created during selector evaluation.
Reviewed By: csantos42, maxijb
Differential Revision: D21919550
fbshipit-source-id: 306ce337e28ed422b3e30e1fa9f20dc38abfb0fc
Summary:
Add preliminary support for effects to scoped atoms.
Note that we pass the handlers through directly. So, the typing is off since handlers are typed for `T` while the scope atom stores `T` or `ScopedAtomTaggedValue<T>`. We can't marshal the underlying type like we do now with validators because we don't want to hard-code the logic for how validation is done when initializing the value.
One difference is that the persisted initialized value is only set for the current scope. If the user changes the scope then the default value is used.
Current plan is to hack in support for `ScopedAtomTaggedValue` in the URL Persistence library and ignore the Flow errors for now. Eventually we hope to properly implemented a scoped atom that doesn't try to package all values in a single atom as well as provides some built-in support for backward-compatibility of changing the scope rules. But, let's go with a gradual migration.
Reviewed By: csantos42
Differential Revision: D21919547
fbshipit-source-id: c7b745066c7a7b491fa9feb2aa9def215a4855a0
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/380
Introduce the concept of effects for Recoil Atoms based on [this proposal](https://fb.quip.com/bauvAeBEX5Ee). This is a similar concept of using React `useEffect()` for side-effects on components, only for atoms. It can be challenging to use `useEffect()` with Recoil atoms because the Recoil hierarchy is orthogonal to the React component hierarchy, and we have to worry about lifetime and singleton patterns. Recoil Atom Effects are an array of callbacks which will be called the first time the atom is used with a `<RecoilRoot>`, so they are always part of a React context. Atom Effects can be used for:
* Synchronization with browser URI history
* Persistence to local storage
* Bi-directional synchronization with remote asynchronous state
* Maintaining state history
* other side-effects such as "set on get"
* &c.
# Proposed Atom Effects
```
type AtomOptions<T> = {
key: string,
default: T | RecoilValue<T> | Promise<T>,
...
effects: $ReadOnlyArray<AtomEffect<T>>,
};
// Effect is called the first time node is used with a <RecoilRoot>
type AtomEffect<T> = ({
node: RecoilState<T>,
trigger: 'get' | 'set',
// Can call either synchronously to initialize value or async to change it later
setSelf: (T | DefaultValue | (T => T | DefaultValue)) => void,
resetSelf: () => void,
getSnapshot: () => Snapshot,
// Subscribe to events
// Called when React batch ends, but before global RecoilTransactionObserver
onSet: ((newValue: T | DefaultValue, oldValue: T | DefaultValue) => void) => void,
}) => void | () => void; // Return optional cleanup handler
```
### Current Diff
This diff introduces a subset of the atom effect functionality:
```
// Effect is called the first time node is used with a <RecoilRoot>
type AtomEffect<T> = ({
node: RecoilState<T>,
trigger: 'get' | 'set',
setSelf: (T | DefaultValue | (T => T | DefaultValue)) => void,
resetSelf: () => void,
getSnapshot: () => Snapshot,
}) => void;
```
### Example Usage
Example use of an atom as a local cache of state that can be bi-directionally synchronized with remote async storage.
```
const myAtom = atom({
key: 'MyAtom',
default: undefined,
effects: [({node, setSelf, onSet}) => {
const storage = getStorage(node.key);
// set a value for synchronous init that overrides default
setSelf(storage.get(value));
// Subscribe to storage updates
storage.subscribe(value => setSelf(value));
// Subscribe local changes to update storage
onSet(value =>
value === undefined
? storage.reset()
: storage.set(value)
);
// Subscribe to cleanup
onCleanup(() => storage.unsubscribe());
}],
});
```
Reviewed By: csantos42
Differential Revision: D21919545
fbshipit-source-id: b500602d7ca91972d28790ee3f895517fdc52320
Summary:
Add flow to GitHub actions
Also gkx is needed to be resolved correctly so flow step would pass green
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/338
Reviewed By: bezi
Differential Revision: D22176735
Pulled By: aaronabramov
fbshipit-source-id: d91861485223dfdbb0258f8b6f6153600910e84f
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/363
Add unit test for `selectorFamily()` with an array parameter
Reviewed By: davidmccabe
Differential Revision: D22133734
fbshipit-source-id: 010f650536c96efde17991e18e3280151a22cd3a
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/362
Organize `stableStringify()` unit tests and add test for Iterables
Reviewed By: davidmccabe
Differential Revision: D22133358
fbshipit-source-id: 7281cb8fb29722ce9458f81faf9cd9ddb22ba960
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/360
Cleanup some warnings such as order requires and returned promises for testing
Reviewed By: davidmccabe
Differential Revision: D22129498
fbshipit-source-id: 2e78ec0cfbcad72734e1aa142e63cb3f47e7c629
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/361
Only deep freeze stored values during development
Reviewed By: davidmccabe
Differential Revision: D22129190
fbshipit-source-id: c7274acf8ee1dd605d3932fc9a90f4badc52230b
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/379
Provide new Link components for Recoil URL persistence based on [this proposal](https://fb.quip.com/UwKaAGjK0L0E). There are two variants:
# `<LinkToRecoilSnapshot>`
A Link component based on the provided `uriFromSnapshot` mapping of a URI from a Recoil Snapshot.
The Link element renders an anchor element. But instead of an href, use a `snapshot` property. When clicked, the Link element updates the current state to the snapshot without loading a new document.
The href property of the anchor will set using `uriFromSnapshot`. This allows users to copy the link, choose to open in a new tab, &c.
If an `onClick` handler is provided, it is called before the state transition and may call preventDefault on the event to stop the state transition.
# `<LinkToRecoilStateChange>`
A Link component based on the provided `uriFromSnapshot` mapping of a URI from a Recoil Snapshot.
The Link element renders an anchor element. But instead of an href, use a `stateChange` property. When clicked, the Link element updates the current state based on the `stateChange` callback without loading a new document. `stateChange` is a function which takes a `MutableSnapshot` that can be used to read the current state and set or update any changes.
The href property of the anchor will set using `uriFromSnapshot`. This allows users to copy the link, choose to open in a new tab, &c.
If an `onClick` handler is provided, it is called before the state transition and may call preventDefault on the event to stop the state transition.
*Note that, because the link renders the href based on the current state snapshot it is re-rendered whenever any state change is made. Keep the performance implications of this in mind.*
Reviewed By: habond
Differential Revision: D21784015
fbshipit-source-id: 9cd6ff2a9fa92bb094175472416a639a2a4f8142
Summary:
The only problem of eslint is missing fb-www package. It might be resolved once the corresponding package will be published by adding it to devDeps. Pls let me know if something goes against internal fb build strategy
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/368
Reviewed By: bezi
Differential Revision: D22146011
Pulled By: aaronabramov
fbshipit-source-id: f6d2333b11d1e9f5026e302f7d8c011d8cfa2c1f
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/313
Drop support for loading URLs with persisted values containing legacy parameterizedAtom values. This was deprecated 3 months ago, past our support agreement for stale links.
Reviewed By: csantos42
Differential Revision: D21849217
fbshipit-source-id: 62241c2d205805d56571cfef7390ef7103f8c251
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/333
Add tests to confirm that `useRecoilCallback()` receives a snapshot that is consistent with the stable and committed state that was in place at the begining of the current transaction. However, the updater form of setting atoms gets a parameter with the previous value that represents previous state changes in that same transaction.
Reviewed By: aaronabramov
Differential Revision: D22062359
fbshipit-source-id: 1ec076256a0056100e2c64c04c3bc304e9461bd9
Summary:
`gkx` module was added to the codebase and it doesn't exist outside www.
adding all necessary mocks to fix the build
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/332
Reviewed By: drarmstr
Differential Revision: D22062299
Pulled By: aaronabramov
fbshipit-source-id: e61dbca322321e4a79d01b3c6d5ae954053c2bfb
Summary:
Documentation:
📃 Added licence badge in README.md
📃 Added the installation and contributing guide in README
This is my first ever contribution to open source. It is quite small, but it'll get me started. 🙌
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/189
Reviewed By: aaronabramov
Differential Revision: D21861635
Pulled By: davidmccabe
fbshipit-source-id: 5197f487cf40c1e3688c929d044e517b7d6e25b6
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/312
Deprecate the old `initializeState_DEPRECATED` prop in `<RecoilRoot>` and add a new `initializeSnapshot` with the following signature:
```
initializeState: MutableSnapshot => void,
```
Alternative signatures could have been:
```
initializeState: Snapshot => Snapshot,
```
But this is more consistent with `Snapshot.map()` and we don't want to encourage long-term `Snapshot` storage. We might also consider
```
initialSnapshot: Snapshot,
```
But, this would require us to expose an API for the user to construct a fresh `Snapshot`, which we want to avoid for now. Use of that pattern of building a `Snapshot` from scratch a lot would miss opportunities to optimize snapshots to encode deltas of a chained history instead of duplicate full state when cloning.
The new API uses the `MutableSnapshot` to be consistent with `Snapshot` usage elsewhere in the API. In practice, it's mostly the same as the previous `initializeState` prop when using the `set` method. However, the `setUnvalidatedAtomValues` is not provided. This functionality will instead be provided via [atom effects](https://fb.quip.com/bauvAeBEX5Ee), so it is more granular, composable, and co-located. We can remove the deprecated version when we finish migration to the new URL persistence library.
One remaining optimization would be that the state is stored using `useRef()`, which doesn't use a callback for the initial default parameter that `useState()` does. This means it is recomputing the state initialization each time the `<RecoilRoot>` is re-rendered. The re-computation is ignored, but is wasted effort. (The old implementation appears to have a bug that rendering this again, while ignoring the re-computed state, may cause components that subscribe to initialized atoms to also re-render; though I suppose we're re-rendering the root anyway at that point)
Reviewed By: csantos42
Differential Revision: D21856852
fbshipit-source-id: eecd999bc0c3f31670c6eb5ac51d2d38a3e77891
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/311
Add `useRecoilSnapshotAndSubscribe()` hook. This was originally part of [this proposal](https://fb.quip.com/UwKaAGjK0L0E). We removed it thinking that `useRecoilTransactionObserver()` would be sufficient. However, we need this hook to be able to synchronously get a snapshot during render in order to support server-side rendering. We could drop `useRecoilTransactionObserver()` and only use this hook. However, that one has a more convenient API for persistence/debug tools. So, keep both as a convenience at the expense of a larger API surface area.
```
declare function useRecoilSnapshotAndSubscribe(): Snapshot;
```
Label hook as `useRecoilSnapshotAndSubscribe()` to make more obvious to the user that using this hook will also subscribe that component to all transactions. It should be used sparingly.
In the future, we plan to add a parameter to `useRecoilSnapshotAndSubscribe()` and `useRecoilTransactionObserver()` to control debouncing or condition when to update the subscription.
Reviewed By: csantos42
Differential Revision: D21783137
fbshipit-source-id: 51096db1fcd50b59017a9b2ef440f1d9418cbbb9
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/310
Add `useGotoRecoilSnapshot()` hook as per [this proposal](https://fb.quip.com/UwKaAGjK0L0E)
```
function useGotoRecoilSnapshot(): Snapshot => void
```
Change current store to use the state in the provided `Snapshot`. If any atom values changed, then fire subscriptions to update any currently subscribed components for only those updated atoms.
Note that `nodeToComponentSubscriptions` is maintained as the state is updated to match the `Snapshot`. This won't be necessary to explicitly copy when that state is migrated from the `TreeState` to the `StoreState` for CM.
I think the old implementation had a bug where it would only change atom values that were explicitly "updated" in the snapshot, making it not really a snapshot but a queue of state changes.
Next we will migrate `<Link>` and remove the old deprecated version.
Reviewed By: habond, csantos42
Differential Revision: D21739680
fbshipit-source-id: 5da9b3b13a10af208045413e5a73c65b55bb8c2b
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/309
Initial `useRecoilTransactionObserver()` hook based on [this proposal](https://fb.quip.com/UwKaAGjK0L0E)
```
function useRecoilTransactionObserver(
({
snapshot: Snapshot,
previousSnapshot: Snapshot,
}) => void,
): void
```
This adds support for selectors that the old `useTransactionObservation()` hook doesn't. It contains both `snapshot` and `previousSnapshot`, but the snapshots don't yet have a mechanism for iterating the set of atoms/selectors or dirty atoms. So, keep the old hook for now and migrate users over as we add that functionality.
Also mark `useTransactionSubscription()` as DEPRECATED, we will need to migrate the prototype dev tools support over.
Reviewed By: habond, csantos42
Differential Revision: D21737837
fbshipit-source-id: a9dd99431609e753898edfcb604b38a3508fda52
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/258
add `Snapshot` `asyncMap()` as an async variant of `map()` which allows for easier use of the simpler `getPromise()` vs `getLoadable()` accessor based on [this proposal](https://fb.quip.com/UwKaAGjK0L0E)
```
class Snapshot {
...
map: (MutableSnapshot => void) => Snapshot;
asyncMap: (MutableSnapshot => Promise<void>) => Promise<Snapshot>;
};
```
Reviewed By: davidmccabe
Differential Revision: D21706818
fbshipit-source-id: 132a848c1434a954bcd04e449c4c9a32311014aa
Summary:
Pull Request resolved: https://github.com/facebookexperimental/Recoil/pull/260
Update `useRecoilCallback()` to use the `Snapshot` interface based on [this proposal](https://fb.quip.com/UwKaAGjK0L0E)
```
type CallbackInterface = {
set: <T>(RecoilState<T>, T | DefaultValue | (T) => T | DefaultValue) => void,
reset: <T>(RecoilState<T>) => void,
snapshot: Snapshot,
gotoSnapsot: Snapshot => void // TODO
};
function useRecoilCallback(
CallbackInterface => Args => Return,
): Args => Return
```
Notice that this has a similar interface as the current `useRecoilCallback()` API (with the wrapped callback RFC proposal), but moves the snapshot accessors to use a `Snapshot` object instead. The `set`/`reset` callbacks affect the current state, not the snapshot.
NOTE: This is a breaking change, but we are still on version `0.0.x` for semantic versioning.
Reviewed By: davidmccabe
Differential Revision: D21705808
fbshipit-source-id: 623f5bfdfed9e4c7e9abe8173b07bf02bd492253