Skip to content

Commit 38bcf99

Browse files
committed
chore: Convert RefreshControl to function component
1 parent d8f7183 commit 38bcf99

1 file changed

Lines changed: 86 additions & 74 deletions

File tree

packages/react-native/Libraries/Components/RefreshControl/RefreshControl.js

Lines changed: 86 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import PullToRefreshViewNativeComponent, {
1818
Commands as PullToRefreshCommands,
1919
} from './PullToRefreshViewNativeComponent';
2020
import * as React from 'react';
21+
import {useCallback, useEffect, useRef, useState} from 'react';
2122

2223
const 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 */
7676
export 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

210222
export default RefreshControl;

0 commit comments

Comments
 (0)