UNPKG

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