@@ -18,6 +18,7 @@ import PullToRefreshViewNativeComponent, {
1818 Commands as PullToRefreshCommands ,
1919} from './PullToRefreshViewNativeComponent' ;
2020import * as React from 'react' ;
21+ import { useCallback , useEffect , useRef , useState } from 'react' ;
2122
2223const Platform = require ( '../../Utilities/Platform' ) . default ;
2324
@@ -72,7 +73,6 @@ type RefreshControlBaseProps = Readonly<{
7273 progressViewOffset ?: ?number ,
7374} > ;
7475
75- /** @build -types emit-as-interface Uniwind compatibility */
7676export type RefreshControlProps = Readonly < {
7777 ...ViewProps ,
7878 ...RefreshControlPropsIOS ,
@@ -125,86 +125,98 @@ export type RefreshControlProps = Readonly<{
125125 * __Note:__ `refreshing` is a controlled prop, this is why it needs to be set to true
126126 * in the `onRefresh` function otherwise the refresh indicator will stop immediately.
127127 */
128- class RefreshControl extends React . Component < RefreshControlProps > {
129- _nativeRef : ?React . ElementRef <
130- | typeof PullToRefreshViewNativeComponent
131- | typeof AndroidSwipeRefreshLayoutNativeComponent ,
132- > ;
133- _lastNativeRefreshing : boolean = false ;
134-
135- componentDidMount ( ) {
136- this . _lastNativeRefreshing = this . props . refreshing ;
137- }
128+ const RefreshControl : component ( ...RefreshControlProps ) = ( {
129+ // Android only props
130+ enabled,
131+ colors,
132+ progressBackgroundColor,
133+ size,
134+ // iOS only props
135+ tintColor,
136+ titleColor,
137+ title,
138+ // Common props
139+ onRefresh,
140+ refreshing,
141+ ...viewProps
142+ } : RefreshControlProps ) : React . Node => {
143+ const ref =
144+ useRef <
145+ React . ElementRef <
146+ | typeof PullToRefreshViewNativeComponent
147+ | typeof AndroidSwipeRefreshLayoutNativeComponent ,
148+ > ,
149+ > ( null ) ;
150+
151+ const [ rerender , forceRerender ] = useState ( 0 ) ;
152+ const nativeRefreshingState = useRef ( refreshing ) ;
153+
154+ const handleRefresh = useCallback ( ( ) => {
155+ nativeRefreshingState . current = true ; // Native state has changed to `true` on `onRefresh` callback.
156+
157+ // $FlowFixMe[unused-promise]
158+ onRefresh ?. ( ) ;
159+
160+ // The native component will start refreshing so force an update to
161+ // make sure it stays in sync with the js component.
162+ forceRerender ( val => val + 1 ) ;
163+ } , [ onRefresh ] ) ;
164+
165+ // RefreshControl is a controlled component so if the native refreshing
166+ // value doesn't match the current js refreshing prop update it to
167+ // the js value.
168+ useEffect ( ( ) => {
169+ const viewRef = ref . current ;
170+ if ( ! viewRef ) {
171+ return ;
172+ }
138173
139- componentDidUpdate ( prevProps : RefreshControlProps ) {
140- // RefreshControl is a controlled component so if the native refreshing
141- // value doesn't match the current js refreshing prop update it to
142- // the js value.
143- if ( this . props . refreshing !== prevProps . refreshing ) {
144- this . _lastNativeRefreshing = this . props . refreshing ;
145- } else if (
146- this . props . refreshing !== this . _lastNativeRefreshing &&
147- this . _nativeRef
148- ) {
149- if ( Platform . OS === 'android' ) {
150- AndroidSwipeRefreshLayoutCommands . setNativeRefreshing (
151- this . _nativeRef ,
152- this . props . refreshing ,
153- ) ;
154- } else {
155- PullToRefreshCommands . setNativeRefreshing (
156- this . _nativeRef ,
157- this . props . refreshing ,
158- ) ;
159- }
160- this . _lastNativeRefreshing = this . props . refreshing ;
174+ // Do nothing when a native state is the same as a `refreshing` prop
175+ if ( nativeRefreshingState . current === refreshing ) {
176+ return ;
161177 }
162- }
163178
164- render ( ) : React . Node {
165- if ( Platform . OS === 'ios' ) {
166- const { enabled, colors, progressBackgroundColor, size, ...props } =
167- this . props ;
168- return (
169- < PullToRefreshViewNativeComponent
170- { ...props }
171- ref = { this . _setNativeRef }
172- onRefresh = { this . _onRefresh }
173- />
179+ // Otherwise a JS component has to sync a native state with an actual `refreshing` value
180+ if ( Platform . OS === 'android' ) {
181+ AndroidSwipeRefreshLayoutCommands . setNativeRefreshing (
182+ viewRef ,
183+ refreshing ,
174184 ) ;
175185 } else {
176- const { tintColor, titleColor, title, ...props } = this . props ;
177- return (
178- < AndroidSwipeRefreshLayoutNativeComponent
179- { ...props }
180- ref = { this . _setNativeRef }
181- onRefresh = { this . _onRefresh }
182- />
183- ) ;
186+ PullToRefreshCommands . setNativeRefreshing ( viewRef , refreshing ) ;
184187 }
185- }
186188
187- _onRefresh = ( ) => {
188- this . _lastNativeRefreshing = true ;
189-
190- // $FlowFixMe[unused-promise]
191- this . props . onRefresh && this . props . onRefresh ( ) ;
189+ nativeRefreshingState . current = refreshing ;
190+ } , [ refreshing , rerender ] ) ;
191+
192+ if ( Platform . OS === 'ios' ) {
193+ return (
194+ < PullToRefreshViewNativeComponent
195+ { ...viewProps }
196+ refreshing = { refreshing }
197+ tintColor = { tintColor }
198+ titleColor = { titleColor }
199+ title = { title }
200+ ref = { ref }
201+ onRefresh = { handleRefresh }
202+ />
203+ ) ;
204+ }
192205
193- // The native component will start refreshing so force an update to
194- // make sure it stays in sync with the js component.
195- this . forceUpdate ( ) ;
196- } ;
197-
198- _setNativeRef = (
199- ref : ?React . ElementRef <
200- | typeof PullToRefreshViewNativeComponent
201- | typeof AndroidSwipeRefreshLayoutNativeComponent ,
202- > ,
203- ) => {
204- this. _nativeRef = ref ;
205- } ;
206- }
207-
208- export type RefreshControlInstance = RefreshControl ;
206+ return (
207+ < AndroidSwipeRefreshLayoutNativeComponent
208+ { ...viewProps }
209+ refreshing = { refreshing }
210+ enabled = { enabled }
211+ colors = { colors }
212+ progressBackgroundColor = { progressBackgroundColor }
213+ size = { size }
214+ ref = { ref }
215+ onRefresh = { handleRefresh }
216+ />
217+ ) ;
218+ } ;
219+
220+ RefreshControl . displayName = 'RefreshControl' ;
209221
210222export default RefreshControl ;
0 commit comments