52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/docs/api-reference/core/atom.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: atom(options)
3 | sidebar_label: atom()
4 | ---
5 |
6 | Returns writeable Recoil state.
7 |
8 | ---
9 |
10 | - `options`
11 | - `key`: A unique string used to identify the atom internally. This string should be unique with respect to other atoms and selectors in the entire application.
12 | - `default`: The initial value of the atom.
13 |
14 | Most often, you'll use the following hooks to interact with atoms:
15 |
16 | - [`useRecoilState()`](/docs/api-reference/core/useRecoilState): use this hook when you intend on both reading and writing to the atom. This hook subscribes the component to the atom.
17 | - [`useRecoilValue()`](/docs/api-reference/core/useRecoilValue): use this hook when you intend on only reading the atom. This hook subscribes the component to the atom.
18 | - [`useSetRecoilState()`](/docs/api-reference/core/useSetRecoilState): use this hook when you intend on only writing to the atom.
19 | - [`useResetRecoilState()`](/docs/api-reference/core/useResetRecoilState): use this hook to reset an atom to its default value.
20 |
21 | For rare cases where you need to read an atom's value without subscribing to the component, see [`useRecoilCallback()`](/docs/api-reference/core/useRecoilCallback).
22 |
23 | ### Example
24 |
25 | ```jsx
26 | import {atom, useRecoilState} from 'recoil';
27 |
28 | const counter = atom({
29 | key: 'myCounter',
30 | default: 0,
31 | });
32 |
33 | function Counter() {
34 | const [count, setCount] = useRecoilState(counter);
35 | const incrementByOne = () => setCount(count + 1);
36 |
37 | return (
38 |
39 | Count: {count}
40 |
41 |
42 |
43 | );
44 | }
45 | ```
46 |
--------------------------------------------------------------------------------
/docs/api-reference/core/useRecoilState.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: useRecoilState()
3 | sidebar_label: useRecoilState()
4 | ---
5 |
6 | Returns a tuple where the first element is the value of state and the second element is a setter function that will update the value of the given state when called.
7 |
8 | This hook will implicitly subscribe the component to the given state.
9 |
10 | ---
11 |
12 | - `state`: an [`atom`](/docs/api-reference/core/atom) or a _writeable_ [`selector`](/docs/api-reference/core/selector). Writeable selectors are selectors that were have both a `get` and `set` in their definition while read-only selectors only have a `get`.
13 |
14 | This is the recommended hook to use when a component intends to read and write state.
15 |
16 | ### Example
17 |
18 | ```jsx
19 | import {atom, selector, useRecoilState} from 'recoil';
20 |
21 | const tempFahrenheit = atom({
22 | key: 'tempFahrenheit',
23 | default: 32,
24 | });
25 |
26 | const tempCelcius = selector({
27 | key: 'tempCelcius',
28 | get: ({get}) => ((get(tempFahrenheit) - 32) * 5) / 9,
29 | set: ({set}, newValue) => set(tempFahrenheit, (newValue * 9) / 5 + 32),
30 | });
31 |
32 | function TempCelcius() {
33 | const [tempF, setTempF] = useRecoilState(tempFahrenheit);
34 | const [tempC, setTempC] = useRecoilState(tempCelcius);
35 |
36 | const addTenCelcius = () => setTempC(tempC + 10);
37 | const addTenFahrenheit = () => setTempF(tempF + 10);
38 |
39 | return (
40 |
49 | );
50 | }
51 | ```
52 |
--------------------------------------------------------------------------------
/docs/api-reference/core/useRecoilValueLoadable.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: useRecoilValueLoadable()
3 | sidebar_label: useRecoilValueLoadable()
4 | ---
5 |
6 | ```jsx
7 | function useRecoilValueLoadable(state: RecoilValue): Loadable
8 | ```
9 |
10 | Returns a `Loadable`.
11 |
12 | This hook is intended to be used for reading the value of asynchronous selectors. This hook will implicitly subscribe the component to the given state.
13 |
14 | Unlike `useRecoilValue()`, this hook will not throw a `Promise` when reading from a pending asynchronous selector (for the purpose of working alongside Suspense). Instead, this hook returns a `Loadable`, which is an object with the following interface:
15 |
16 | - `state`: indicates the status of the selector. Possible values are `'hasValue'`, `'hasError'`, `'loading'`.
17 | - `contents`: The value represented by this `Loadable`. If the state is `hasValue`, it is the actual value, if the state is `hasError` it is the `Error` object that was thrown, and if the state is `loading`, then it is a `Promise` of the value.
18 | - `getValue()`: if there is an error, this function throws the error. If selector is still loading, it throws a Promise. Otherwise it returns the value that the selector resolved to.
19 | - `toPromise()`: returns a `Promise` that will resolve when the selector has resolved. If the selector is synchronous or has already resolved, it returns a `Promise` that resolves immediately.
20 |
21 | ---
22 |
23 | - `state`: an [`atom`](/docs/api-reference/core/atom) or [`selector`](/docs/api-reference/core/selector) that _may_ have some asynchronous operations. The status of the returned loadable will depend on the status of the provided state selector.
24 |
25 | ### Example
26 |
27 | ```jsx
28 | function UserInfo({userID}) {
29 | const userNameLoadable = useRecoilValueLoadable(userNameQuery(userID));
30 | switch (userNameLoadable.state) {
31 | case 'hasValue':
32 | return
{userNameLoadable.contents}
;
33 | case 'loading':
34 | return
Loading...
;
35 | case 'hasError':
36 | throw userNameLoadable.contents;
37 | }
38 | }
39 | ```
40 |
--------------------------------------------------------------------------------
/docs/api-reference/core/selector.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: selector(options)
3 | sidebar_label: selector()
4 | ---
5 |
6 | Returns writeable or read-only Recoil state, depending on the options passed to the function.
7 |
8 | Selectors represent **derived state**. You can think of derived state as the output of passing state to a pure function that modifies the given state in some way.
9 |
10 | ---
11 |
12 | - `options`
13 | - `key`: A unique string used to identify the atom internally. This string should be unique with respect to other atoms and selectors in the entire application.
14 | - `get`: A function that is passed an object as the first parameter containing the following properties:
15 | - `get`: a function used to retrieve values from other atoms/selectors. All atoms/selectors passed to this function will be implicitly added to a list of **dependencies** for the selector. If any of the selector's dependencies change, the selector will re-evaluate.
16 | - `set?`: If this property is set, the selector will return **writeable** state. A function that is passed an object as the first parameter containing the following properties:
17 | - `get`: a function used to retrieve values from other atoms/selectors. This function will not subscribe the selector to the given atoms/selectors.
18 | - `set`: a function used to set the values of Recoil state. The first parameter is the Recoil state and the second parameter is the new value.
19 |
20 | ### Example (Synchronous)
21 |
22 | ```jsx
23 | import {atom, selector, useRecoilState} from 'recoil';
24 |
25 | const tempFahrenheit = atom({
26 | key: 'tempFahrenheit',
27 | default: 32,
28 | });
29 |
30 | const tempCelcius = selector({
31 | key: 'tempCelcius',
32 | get: ({get}) => ((get(tempFahrenheit) - 32) * 5) / 9,
33 | set: ({set}, newValue) => set(tempFahrenheit, (newValue * 9) / 5 + 32),
34 | });
35 |
36 | function TempCelcius() {
37 | const [tempF, setTempF] = useRecoilState(tempFahrenheit);
38 | const [tempC, setTempC] = useRecoilState(tempCelcius);
39 |
40 | const addTenCelcius = () => setTempC(tempC + 10);
41 | const addTenFahrenheit = () => setTempF(tempF + 10);
42 |
43 | return (
44 |
119 | );
120 | }
121 |
122 | function replaceItemAtIndex(arr, index, newValue) {
123 | return [...arr.slice(0, index), newValue, ...arr.slice(index + 1)];
124 | }
125 |
126 | function removeItemAtIndex(arr, index) {
127 | return [...arr.slice(0, index), ...arr.slice(index + 1)];
128 | }
129 | ```
130 |
131 | 有了它,我们有了一个功能齐全的待办事项列表!下一节我们将介绍如何使用 selectors 将列表更新到一个新的水平。
132 |
--------------------------------------------------------------------------------
/docs/api-reference/utils/selectorFamily.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: selectorFamily()
3 | sidebar_label: selectorFamily()
4 | ---
5 |
6 | Returns a function that returns a read-only `RecoilValueReadOnly` or writeable `RecoilState` selector.
7 |
8 | A `selectorFamily` is a powerful pattern that is similar to a `selector`, but allows you to pass parameters to the `get` and `set` callbacks of a `selector`.
9 |
10 | ```jsx
11 | function selectorFamily({
12 | key: string,
13 |
14 | get: Parameter => ({get: GetRecoilValue}) => Promise | RecoilValue | T,
15 |
16 | dangerouslyAllowMutability?: boolean,
17 | }): RecoilValueReadOnly
18 | ```
19 |
20 | ```jsx
21 | function selectorFamily({
22 | key: string,
23 |
24 | get: Parameter => ({get: GetRecoilValue}) => Promise | RecoilValue | T,
25 |
26 | set: Parameter => (
27 | {
28 | get: GetRecoilValue,
29 | set: SetRecoilValue,
30 | reset: ResetRecoilValue,
31 | },
32 | newValue: T | DefaultValue,
33 | ) => void,
34 |
35 | dangerouslyAllowMutability?: boolean,
36 | }): RecoilState
37 | ```
38 |
39 | Where
40 |
41 | ```jsx
42 | type ValueOrUpdater =
43 | | T
44 | | DefaultValue
45 | | ((prevValue: T) => T | DefaultValue);
46 | type GetRecoilValue = (RecoilValue) => T;
47 | type SetRecoilValue = (RecoilState, ValueOrUpdater) => void;
48 | type ResetRecoilValue = (RecoilState) => void;
49 | ```
50 |
51 | ---
52 |
53 | The `selectorFamily()` utility returns a function which can be called with user-defined parameters and returns a selector. Each unique parameter value will return the same memoized selector instance.
54 |
55 | - `options`
56 | - `key`: A unique string used to identify the atom internally. This string should be unique with respect to other atoms and selectors in the entire application.
57 | - `get`: A function that is passed an object of named callbacks that returns the value of the selector, the same as the `selector()` interface. This is wrapped by a function which is passed the parameter from calling the selector family function.
58 | - `set?`: An optional function that will produce writeable selectors when provided. It should be a function that takes an object of named callbacks, same as the `selector()` interface. This is again wrapped by another function with gets the parameters from calling the selector family function.
59 |
60 | ## Example
61 |
62 | ```jsx
63 | const myNumberState = atom({
64 | key: 'MyNumber',
65 | default: 2,
66 | });
67 |
68 | const myMultipliedState = selectorFamily({
69 | key: 'MyMultipliedNumber',
70 | get: (multiplier) => ({get}) => {
71 | return get(myNumberState) * multiplier;
72 | },
73 |
74 | // optional set
75 | set: (multiplier) => ({set}, newValue) => {
76 | set(myNumberState, newValue / multiplier);
77 | },
78 | });
79 |
80 | function MyComponent() {
81 | // defaults to 2
82 | const number = useRecoilValue(myNumberState);
83 |
84 | // defaults to 200
85 | const multipliedNumber = useRecoilValue(myMultipliedState(100));
86 |
87 | return
...
;
88 | }
89 | ```
90 |
91 | ## Query Example
92 |
93 | Selector Families are also useful to use for passing parameters to queries:
94 |
95 | ```jsx
96 | const myDataQuery = selectorFamily({
97 | key: 'MyDataQuery',
98 | get: (queryParameters) => async ({get}) => {
99 | const response = await asyncDataRequest(queryParameters);
100 | if (response.error) {
101 | throw response.error;
102 | }
103 | return response.data;
104 | },
105 | });
106 |
107 | function MyComponent() {
108 | const data = useRecoilValue(myDataQuery({userID: 132}));
109 | return
...
;
110 | }
111 | ```
112 |
--------------------------------------------------------------------------------
/docs/guides/persistence.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: State Persistence
3 | sidebar_label: State Persistence
4 | ---
5 |
6 | Recoil allows you to persist application state using atoms.
7 |
8 | ---
9 |
10 | ## _IMPORTANT NOTE_
11 |
12 | **_This API is currently under development and still evolving. Please stay tuned..._**
13 |
14 | ---
15 |
16 | ## Saving State
17 |
18 | To save state, subscribe to atom changes and record the new state. You could use React effects to subscribe to individual atoms (_See [Asynchronous State Sync](asynchronous-state-sync)_). However, Recoil provides a hook to allow you to subscribe to state changes for all atoms using **`useTransactionObservation_UNSTABLE()`**. (**_NOTE_**: _This API is currently under development_).
19 |
20 | The subscription callback provides all of the atom state and tells you which atoms changed. From this you can save the changes with the storage and serialization of your preference. Here is an example of a basic implementation using JSON:
21 |
22 | ```jsx
23 | function PersistenceObserver() {
24 | useTransactionObservation_UNSTABLE(
25 | ({atomValues, atomInfo, modifiedAtoms}) => {
26 | for (const modifiedAtom of modifiedAtoms) {
27 | Storage.setItem(
28 | modifiedAtom.key,
29 | JSON.stringify({value: atomValues.get(modifiedAtom)}),
30 | );
31 | }
32 | },
33 | );
34 | }
35 | ```
36 |
37 | _Storage_ could be the browser URL history, [_LocalStorage_](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage), _[AsyncStorage](https://github.com/react-native-community/react-native-async-storage)_ or whichever storage you like.
38 |
39 | - _`atomValues`_ - A Map of the atom keys and values.
40 | - _`modifiedAtoms`_ - Gives you a map containing the modified atoms.
41 | - _`atomInfo`_ - Atom metadata.
42 |
43 | You may not wish to persist all atoms, or some atoms may have different persistence behaviors. You can read metadata (**_NOTE_**: _new API coming soon_) to get options from each atom.
44 |
45 | ## Reestoring State
46 |
47 | After you ensure that you're saving your state to your storage, you need to recover it when loading the app. This can be done using the **`initializeState`** prop on thee **``** component. (**_NOTE_**: _API changes coming soon_).
48 |
49 | `initializeState` is a function that provides a **`set`** method to provide the initial atom value for an atom key. Pass the key for the atom and the stored value to this callback and it will initialize the atom to the restored state.
50 |
51 | Note that it is important to use this prop instead of just manually setting atom values in an effect. Otherwise there will be an initial render without the restored state which can cause flicker or be invalid.
52 |
53 | Here is a basic example:
54 |
55 | ```jsx
56 | const initializeState = ({set}) => {
57 | Storage.getAllKeys((error, keys) => {
58 | const promises = keys.map((key) => Storage.getItem(key));
59 | Promise.all(promises).then((values) => {
60 | for (let i = 0; i < promises.length; i++) {
61 | const key = keys[i];
62 | const value = JSON.parse(values[i]).value;
63 | set({key}, value);
64 | }
65 | });
66 | });
67 | };
68 |
69 | return (
70 |
71 |
72 |
73 |
74 | );
75 | ```
76 |
77 | ## Syncing State
78 |
79 | You may also wish for asynchronous updates of the storage, such as the user pressing the browser back button with URL persistence, to sync with the current app state. You can use a React effect to subscribe to these changes and update the value of any modified atoms directly.
80 |
81 | _Example coming soon..._
82 |
83 | ## Backward-Compatibility and Value Validation
84 |
85 | What if your state storage is not reliable? Or what if you change which atoms or types you are using and need to work with previously persisted state? More documentation and examples on how to handle this coming soon as the API is fianlized...
86 |
--------------------------------------------------------------------------------
/docs/api-reference/utils/atomFamily.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: atomFamily()
3 | sidebar_label: atomFamily()
4 | ---
5 |
6 | Returns a function that returns a writeable `RecoilState` atom.
7 |
8 | ```jsx
9 | function atomFamily({
10 | key: string,
11 |
12 | default:
13 | | RecoilValue
14 | | Promise
15 | | T
16 | | (Parameter => T | RecoilValue | Promise),
17 |
18 | dangerouslyAllowMutability?: boolean,
19 | }): RecoilState
20 | ```
21 |
22 | ---
23 |
24 | - `options`
25 | - `key`: A unique string used to identify the atom internally. This string should be unique with respect to other atoms and selectors in the entire application.
26 | - `default`: The initial value of the atom. It may either be a value directly, a `RecoilValue` or `Promise` that represents the default value, or a function to get the default value. The callback function is passed a copy of the parameter used when the `atomFamily` function is called.
27 |
28 | An `atom` represents a piece of state with _Recoil_. An atom is created and registered per `` by your app. But, what if your state isn’t global? What if your state is associated with a particular instance of a control, or with a particular element? For example, maybe your app is a UI prototyping tool where the user can dynamically add elements and each element has state, such as its position. Idealy, each element would get its own atom of state. You could implement this yourself via a memoization pattern. But, _Recoil_ provides this pattern for you with the `atomFamily` utility. An Atom Family represents a collection of atoms. When you call `atomFamily` it will return a function which provides the `RecoilState` atom based on the parameters you pass in.
29 |
30 | ## Example
31 |
32 | ```jsx
33 | const elementPositionStateFamily = atomFamily({
34 | key: 'ElementPosition',
35 | default: [0, 0],
36 | });
37 |
38 | function ElementListItem({elementID}) {
39 | const position = useRecoilValue(elementPositionStateFamily(elementID));
40 | return (
41 |
185 | );
186 | }
187 |
188 | function replaceItemAtIndex(arr, index, newValue) {
189 | return [...arr.slice(0, index), newValue, ...arr.slice(index + 1)];
190 | }
191 |
192 | function removeItemAtIndex(arr, index) {
193 | return [...arr.slice(0, index), ...arr.slice(index + 1)];
194 | }
195 |
196 | let id = 0;
197 | function getId() {
198 | return id++;
199 | }
200 |
201 | export default App;
202 |
--------------------------------------------------------------------------------
/static/img/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/guides/asynchronous-state-sync.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Asynchronous State Sync
3 | sidebar_label: Asynchronous State Sync
4 | ---
5 |
6 | Recoil [atoms](/docs/api-reference/core/atom) represent local application state. Your application may have remote or server-side state as well, such as via a RESTful API. Consider synchronizing the remote state with Recoil atoms. Doing this allows you to easily access or write to the state from React components using the `useRecoilState()` hook, or use that state as input to the Recoil data-flow graph for other derived state selectors. If you're looking to [query a database or server for read-only data](asynchronous-data-queries), consider using asynchronous selectors.
7 |
8 | ## Local State Example
9 |
10 | This example provides the friend status as local state only.
11 |
12 | ```jsx
13 | const currentUserIDState = atom({
14 | key: 'CurrentUserID',
15 | default: null,
16 | });
17 |
18 | function CurrentUserInfo() {
19 | const [currentUserID] = useRecoilState(currentUserIDState);
20 | return
Current User: {currentUserID}
;
21 | }
22 | ```
23 |
24 | ## Sync State From Server
25 |
26 | We can subscribe to asynchronous changes in the remote state and update the atom value to match. This can be done using standard React `useEffect()` hook or other popular libraries.
27 |
28 | ```jsx
29 | function CurrentUserIDSubscription() {
30 | const setCurrentUserID = useSetRecoilState(currentUserIDState);
31 |
32 | useEffect(() => {
33 | RemoteStateAPI.subscribeToCurrentUserID(setCurrentUserID);
34 | // Specify how to cleanup after this effect
35 | return function cleanup() {
36 | RemoteServerAPI.unsubscribeFromFriendStatus(setCurrentUserID);
37 | };
38 | }, []);
39 |
40 | return null;
41 | }
42 |
43 | function MyApp() {
44 | return (
45 |
46 |
47 |
48 |
49 | );
50 | }
51 | ```
52 |
53 | If you want to handle synchronization of multiple atoms in a single place, you can also use the [State Persistence](persistence) pattern.
54 |
55 | ## Bi-Directional Synching
56 |
57 | You can also sync the state so local changes are updated on the server:
58 |
59 | ```jsx
60 | function CurrentUserIDSubscription() {
61 | const [currentUserID, setCurrentUserID] = useRecoilState(currentUserIDState);
62 | let knownServerCurrentUserID;
63 |
64 | // Subscribe server changes to update atom state
65 | useEffect(() => {
66 | function handleUserChange(id) {
67 | knownServerCurrentUserID = id;
68 | setCurrentUserID(id);
69 | }
70 |
71 | RemoteStateAPI.subscribeToCurrentUserID(handleUserChange);
72 | // Specify how to cleanup after this effect
73 | return function cleanup() {
74 | RemoteServerAPI.unsubscribeFromFriendStatus(handleUserChange);
75 | };
76 | }, []);
77 |
78 | // Subscribe atom changes to update server state
79 | useEffect(() => {
80 | if (currentUserID !== knownServerCurrentUserID) {
81 | knownServerCurrentID = currentUserID;
82 | RemoteServerAPI.updateCurrentUser(currentUserID);
83 | }
84 | }, [currentUserID, knownServerCurrentUserID]);
85 |
86 | return null;
87 | }
88 | ```
89 |
90 | ## Synching State with Parameters
91 |
92 | You can also use the [`atomFamily`](/docs/api-reference/utils/atomFamily) helper to sync local state based on parameters.
93 |
94 | ```jsx
95 | const friendStatusState = atomFamily({
96 | key: 'Friend Status',
97 | default: 'offline',
98 | });
99 |
100 | function useFriendStatusSubscription(id) {
101 | const setStatus = useSetRecoilState(friendStatusState(id));
102 |
103 | useEffect(() => {
104 | RemoteStateAPI.subscribeToFriendStatus(id, setStatus);
105 | // Specify how to cleanup after this effect
106 | return function cleanup() {
107 | RemoteServerAPI.unsubscribeFromFriendStatus(id, setStatus);
108 | };
109 | }, []);
110 | }
111 | ```
112 |
113 | ## Data-Flow Graph
114 |
115 | An advantage of using atoms to represent remote state is that you can use it as input for other derived state. The following example will show the current user and friend list based on the current server state. If the server changes the current user it will re-render the entire list, if it only changes the status of a friend then only that list entry will be re-rendered. If a list item is clicked on, it will change the current user locally and will update the server state.
116 |
117 | ```jsx
118 | const userInfoQuery = selectorFamily({
119 | key: 'UserInfoQuery',
120 | get: userID => async ({get}) => {
121 | const response = await myDBQuery({userID});
122 | if (response.error) {
123 | throw response.error;
124 | }
125 | return response;
126 | },
127 | });
128 |
129 | const currentUserInfoQuery = selector({
130 | key: 'CurrentUserInfoQuery',
131 | get: ({get}) => get(userInfoQuery(get(currentUserIDState)),
132 | });
133 |
134 | const friendColorState = selectorFamily({
135 | key: 'FriendColor',
136 | get: friendID => ({get}) => {
137 | const [status] = useRecoilState(friendStatusState(friendID));
138 | return status === 'offline' ? 'red' : 'green';
139 | }
140 | })
141 |
142 | function FriendStatus({friendID}) {
143 | useFriendStatusSubscription(friendID);
144 | const [status] = useRecoilState(friendStatusState(friendID));
145 | const [color] = useRecoilState(friendColorState(friendID));
146 | const [friend] = useRecoilState(userInfoQuery(friendID));
147 | return (
148 |
}>
165 |
166 |
167 |
168 | )}
169 |
170 |
171 | );
172 | }
173 |
174 | function MyApp() {
175 | return (
176 |
177 |
178 | Loading...}>
179 |
180 |
181 |
182 |
183 |
184 | );
185 | }
186 | ```
187 |
--------------------------------------------------------------------------------
/docs/guides/asynchronous-data-queries.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Asynchronous Data Queries
3 | sidebar_label: Asynchronous Data Queries
4 | ---
5 |
6 | Recoil provides a way to map state and derived state to React components via a data-flow graph. What's really powerful is that the functions in the graph can also be asynchronous. This makes it easy to use asynchronous functions in synchronous React component render functions. Recoil allows you to seemlessly mix synchronous and asynchronous functions in your data-flow graph of selectors. Simply return a Promise to a value instead of the value itself from a selector `get` callback, the interface remains exactly the same. Because these are just selectors, other selectors can also depend on them to further transform the data.
7 |
8 | Selectors can be used as one way to incorporate asynchronous data into the Recoil data-flow graph. Please keep in mind that selectors represent pure functions: For a given set of inputs they should always produce the same results (at least for the lifetime of the application). This is important as selector evaluations may execute one or more times, may be restarted, and may be cached. Because of this, selectors are a good way to model read-only DB queries where repeating a query provides consistent data. If you are looking to synchronize local and server state, then please see [Asynchronous State Sync](asynchronous-state-sync) or [State Persistence](persistence).
9 |
10 | ## Synchronous Example
11 |
12 | For example, here is a simple synchronous [atom](/docs/api-reference/core/atom) and [selector](/docs/api-reference/core/selector) to get a user name:
13 |
14 | ```jsx
15 | const currentUserIDState = atom({
16 | key: 'CurrentUserID',
17 | default: 1,
18 | });
19 |
20 | const currentUserNameState = selector({
21 | key: 'CurrentUserName',
22 | get: ({get}) => {
23 | return tableOfUsers[get(currentUserIDState)].name;
24 | },
25 | });
26 |
27 | function CurrentUserInfo() {
28 | const userName = useRecoilValue(currentUserNameState);
29 | return
{userName}
;
30 | }
31 |
32 | function MyApp() {
33 | return (
34 |
35 |
36 |
37 | );
38 | }
39 | ```
40 |
41 | ## Asynchronous Example
42 |
43 | If the user names were stored in some database we need to query, all we need to do is return a `Promise` or use an `async` function. If any dependencies change, the selector will be re-evaluated and execute a new query. The results are cached, so the query will only execute once per unique input.
44 |
45 | ```jsx
46 | const currentUserNameQuery = selector({
47 | key: 'CurrentUserName',
48 | get: async ({get}) => {
49 | const response = await myDBQuery({
50 | userID: get(currentUserIDState),
51 | });
52 | return response.name;
53 | },
54 | });
55 |
56 | function CurrentUserInfo() {
57 | const userName = useRecoilValue(currentUserNameQuery);
58 | return
{userName}
;
59 | }
60 | ```
61 |
62 | The interface of the selector is the same, so the component using this selector doesn't need to care if it was backed with synchronous atom state, derived selector state, or asynchronous queries!
63 |
64 | But, since React render functions are synchronous, what will it render before the promise resolves? Recoil is designed to work with [React Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html) to handle pending data. Wrapping your component with a Suspense boundary will catch any descendents that are still pending and render a fallback UI:
65 |
66 | ```jsx
67 | function MyApp() {
68 | return (
69 |
70 | Loading...}>
71 |
72 |
73 |
74 | );
75 | }
76 | ```
77 |
78 | ## Error Handling
79 |
80 | But what if the request has an error? Recoil selectors can also throw errors which will then be thrown if a component tries to use that value. This can be caught with a React [``](https://reactjs.org/docs/error-boundaries.html). For example:
81 |
82 | ```jsx
83 | const currentUserNameQuery = selector({
84 | key: 'CurrentUserName',
85 | get: async ({get}) => {
86 | const response = await myDBQuery({
87 | userID: get(currentUserIDState),
88 | });
89 | if (response.error) {
90 | throw response.error;
91 | }
92 | return response.name;
93 | },
94 | });
95 |
96 | function CurrentUserInfo() {
97 | const userName = useRecoilValue(currentUserNameQuery);
98 | return
{userName}
;
99 | }
100 |
101 | function MyApp() {
102 | return (
103 |
104 |
105 | Loading...}>
106 |
107 |
108 |
109 |
110 | );
111 | }
112 | ```
113 |
114 | ## Queries with Parameters
115 |
116 | Sometimes you want to be able to query based on parameters that aren't just based on derived state. For example, you may want to query based on the component props. You can do that using the [**`selectorFamily`**](/docs/api-reference/utils/selectorFamily) helper:
117 |
118 | ```jsx
119 | const userNameQuery = selectorFamily({
120 | key: 'UserName',
121 | get: (userID) => async ({get}) => {
122 | const response = await myDBQuery({userID});
123 | if (response.error) {
124 | throw response.error;
125 | }
126 | return response.name;
127 | },
128 | });
129 |
130 | function UserInfo({userID}) {
131 | const userName = useRecoilValue(userNameQuery(userID));
132 | return
{userName}
;
133 | }
134 |
135 | function MyApp() {
136 | return (
137 |
138 |
139 | Loading...}>
140 |
141 |
142 |
143 |
144 |
145 |
146 | );
147 | }
148 | ```
149 |
150 | ## Data-Flow Graph
151 |
152 | Remember, by modeling queries as selectors, we can build a data-flow graph mixing state, derived state, and queries! This graph will automatically update and re-render React components as state is updated.
153 |
154 | The following example will render the current user's name and a list of their friends. If a friend's name is clicked on, they will become the current user and the name and list will be automatically updated.
155 |
156 | ```jsx
157 | const currentUserIDState = atom({
158 | key: 'CurrentUserID',
159 | default: 1,
160 | });
161 |
162 | const userInfoQuery = selectorFamily({
163 | key: 'UserInfoQuery',
164 | get: (userID) => async ({get}) => {
165 | const response = await myDBQuery({userID});
166 | if (response.error) {
167 | throw response.error;
168 | }
169 | return response;
170 | },
171 | });
172 |
173 | const currentUserInfoQuery = selector({
174 | key: 'CurrentUserInfoQuery',
175 | get: ({get}) => get(userInfoQuery(get(currentUserIDState))),
176 | });
177 |
178 | const friendsInfoQuery = selector({
179 | key: 'FriendsInfoQuery',
180 | get: ({get}) => {
181 | const {friendList} = get(currentUserInfoQuery);
182 | const friends = [];
183 | for (const friendID of friendList) {
184 | const friendInfo = get(userInfoQuery(friendID));
185 | friends.push(friendInfo);
186 | }
187 | return friends;
188 | },
189 | });
190 |
191 | function CurrentUserInfo() {
192 | const currentUser = useRecoilValue(currentUserInfoQuery);
193 | const friends = useRecoilValue(friendsInfoQuery);
194 | const setCurrentUserID = useSetRecoilState(currentUserIDState);
195 | return (
196 |
206 | );
207 | }
208 |
209 | function MyApp() {
210 | return (
211 |
212 |
213 | Loading...}>
214 |
215 |
216 |
217 |
218 | );
219 | }
220 | ```
221 |
222 | ## Concurrent Requests
223 |
224 | If you notice in the above example, the `friendsInfoQuery` uses a query to get the info for each friend. But, by doing this in a loop they are essentially serialized. If the lookup is fast, maybe that's ok. If it's expensive, you can use a concurrency helper such as [`waitForAll`](/docs/api-reference/utils/waitForAll), [`waitForNone`](/docs/api-reference/utils/waitForNone), or [`waitForAny`](/docs/api-reference/utils/waitForAny) to run them in parallel or handle partial results. They accept both arrays and named objects of dependencies.
225 |
226 | ```jsx
227 | const friendsInfoQuery = selector({
228 | key: 'FriendsInfoQuery',
229 | get: ({get}) => {
230 | const {friendList} = get(currentUserInfoQuery);
231 | const friends = get(
232 | waitForAll(friendList.map((friendID) => userInfoQuery(friendID))),
233 | );
234 | return friends;
235 | },
236 | });
237 | ```
238 |
239 | ## Without React Suspense
240 |
241 | It is not necessary to use React Suspense for handling pending asynchronous selectors. You can also use the [`useRecoilValueLoadable()`](/docs/api-reference/core/useRecoilValueLoadable) hook to determine the status during rendering:
242 |
243 | ```jsx
244 | function UserInfo({userID}) {
245 | const userNameLoadable = useRecoilValueLoadable(userNameQuery(userID));
246 | switch (userNameLoadable.state) {
247 | case 'hasValue':
248 | return