UNPKG

5.84 kBJavaScriptView Raw
1/**
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @emails oncall+relay
8 *
9 * @format
10 */
11'use strict';
12
13var useRelayEnvironment = require('./useRelayEnvironment');
14
15var warning = require("fbjs/lib/warning");
16
17var _require = require('./FragmentResource'),
18 getFragmentResourceForEnvironment = _require.getFragmentResourceForEnvironment;
19
20var _require2 = require('react'),
21 useEffect = _require2.useEffect,
22 useRef = _require2.useRef,
23 useState = _require2.useState;
24
25var _require3 = require('relay-runtime'),
26 getFragmentIdentifier = _require3.getFragmentIdentifier;
27
28function useFragmentNode(fragmentNode, fragmentRef, componentDisplayName) {
29 var environment = useRelayEnvironment();
30 var FragmentResource = getFragmentResourceForEnvironment(environment);
31 var isMountedRef = useRef(false);
32
33 var _useState = useState(0),
34 forceUpdate = _useState[1];
35
36 var fragmentIdentifier = getFragmentIdentifier(fragmentNode, fragmentRef); // The values of these React refs are counters that should be incremented
37 // under their respective conditions. This allows us to use the counters as
38 // memoization values to indicate if computations for useMemo or useEffect
39 // should be re-executed.
40
41 var mustResubscribeGenerationRef = useRef(0);
42 var shouldUpdateGenerationRef = useRef(0);
43 var environmentChanged = useHasChanged(environment);
44 var fragmentIdentifierChanged = useHasChanged(fragmentIdentifier); // If the fragment identifier changes, it means that the variables on the
45 // fragment owner changed, or the fragment ref points to different records.
46 // In this case, we need to resubscribe to the Relay store.
47
48 var mustResubscribe = environmentChanged || fragmentIdentifierChanged; // We only want to update the component consuming this fragment under the
49 // following circumstances:
50 // - We receive an update from the Relay store, indicating that the data
51 // the component is directly subscribed to has changed.
52 // - We need to subscribe and render /different/ data (i.e. the fragment refs
53 // now point to different records, or the context changed).
54 // Note that even if identity of the fragment ref objects changes, we
55 // don't consider them as different unless they point to a different data ID.
56 //
57 // This prevents unnecessary updates when a parent re-renders this component
58 // with the same props, which is a common case when the parent updates due
59 // to change in the data /it/ is subscribed to, but which doesn't affect the
60 // child.
61
62 if (mustResubscribe) {
63 shouldUpdateGenerationRef.current++;
64 mustResubscribeGenerationRef.current++;
65 } // Read fragment data; this might suspend.
66
67
68 var fragmentResult = FragmentResource.readWithIdentifier(fragmentNode, fragmentRef, fragmentIdentifier, componentDisplayName);
69 var isListeningForUpdatesRef = useRef(true);
70
71 function enableStoreUpdates() {
72 isListeningForUpdatesRef.current = true;
73 var didMissUpdates = FragmentResource.checkMissedUpdates(fragmentResult)[0];
74
75 if (didMissUpdates) {
76 handleDataUpdate();
77 }
78 }
79
80 function disableStoreUpdates() {
81 isListeningForUpdatesRef.current = false;
82 }
83
84 function handleDataUpdate() {
85 if (isMountedRef.current === false || isListeningForUpdatesRef.current === false) {
86 return;
87 } // If we receive an update from the Relay store, we need to make sure the
88 // consuming component updates.
89
90
91 shouldUpdateGenerationRef.current++; // React bails out on noop state updates as an optimization.
92 // If we want to force an update via setState, we need to pass an value.
93 // The actual value can be arbitrary though, e.g. an incremented number.
94
95 forceUpdate(function (count) {
96 return count + 1;
97 });
98 } // Establish Relay store subscriptions in the commit phase, only if
99 // rendering for the first time, or if we need to subscribe to new data
100
101
102 useEffect(function () {
103 isMountedRef.current = true;
104 var disposable = FragmentResource.subscribe(fragmentResult, handleDataUpdate);
105 return function () {
106 // When unmounting or resubscribing to new data, clean up current
107 // subscription. This will also make sure fragment data is no longer
108 // cached for the so next time it its read, it will be read fresh from the
109 // Relay store
110 isMountedRef.current = false;
111 disposable.dispose();
112 }; // NOTE: We disable react-hooks-deps warning because mustResubscribeGenerationRef
113 // is capturing all information about whether the effect should be re-ran.
114 // eslint-disable-next-line react-hooks/exhaustive-deps
115 }, [mustResubscribeGenerationRef.current]);
116
117 if (process.env.NODE_ENV !== "production") {
118 if (fragmentRef != null && fragmentResult.data == null) {
119 process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Expected to have been able to read non-null data for ' + 'fragment `%s` declared in ' + '`%s`, since fragment reference was non-null. ' + "Make sure that that `%s`'s parent isn't " + 'holding on to and/or passing a fragment reference for data that ' + 'has been deleted.', fragmentNode.name, componentDisplayName, componentDisplayName) : void 0;
120 }
121 }
122
123 return {
124 // $FlowFixMe
125 data: fragmentResult.data,
126 disableStoreUpdates: disableStoreUpdates,
127 enableStoreUpdates: enableStoreUpdates,
128 shouldUpdateGeneration: shouldUpdateGenerationRef.current
129 };
130}
131
132function useHasChanged(value) {
133 var _useState2 = useState(value),
134 mirroredValue = _useState2[0],
135 setMirroredValue = _useState2[1];
136
137 var valueChanged = mirroredValue !== value;
138
139 if (valueChanged) {
140 setMirroredValue(value);
141 }
142
143 return valueChanged;
144}
145
146module.exports = useFragmentNode;
\No newline at end of file