react-awesome-ptr
v1.0.63
Published
A pull-to-refresh component that behaves like in native app
Downloads
66
Maintainers
Readme
react-awesome-ptr
A pull-to-refresh react component that behave like almost native app.
- in iOS:
- It utilizes natively supported scroll bounce.
- in the others:
- It makes natural scroll bounce artificially.
- Suitable for all of mobile browsers and web views (inactivated in desktop).
- Automatically disable mobile browser's default pull-to-refresh (only in Android).
- Customizable spinner.
- Support Typescript.
Make sure it's a mobile-only component that works by touch events.
In default pull to refresh supported browsers like Safari in iOS 15, it works only when the browser is not scrolled. If browser is scrolled, browser's default pull to refresh will be triggered.
For the webviews in hybrid apps, it works with no worries.
A complementary custom spinner component
CupertinoSpinner
is included. Feel free to use this component to enhance UX like iOS, but keep in mind that the default spinner shows better performance.
Default Spinner example
CupertinoSpinner example
Usage
Install
npm i --save react-awesome-ptr
import
import PullToRefresh from "react-awesome-ptr";
import "react-awesome-ptr/dist/index.css";
// put the PullToRefresh component where the spinner should be shown
Examples
See examples in a mobile browser, or turn on the browser debugger and toggle device toolbar. You can conveniently open example pages on your mobile by scanning QR codes.
If you see examples in desktop browser, make sure to set the device as any android device.
On the top
import PullToRefresh from "react-awesome-ptr";
import "react-awesome-ptr/dist/index.css";
export const OnTheTop = () => {
const targetRef = useRef<HTMLDivElement>(null);
const [isRefreshing, setIsRefreshing] = useState(false);
const onRefresh = useCallback(() => {
setIsRefreshing(true);
setTimeout(() => {
setIsRefreshing(false);
}, 3000);
}, []);
return (
<>
<PullToRefresh
targetRef={targetRef}
onRefresh={onRefresh}
isRefreshing={isRefreshing}
hasDefaultPullToRefreshPossibly
/>
<div style={{ height: "100vh", background: "pink", padding: 20 }} ref={targetRef}>
Pull in a mobile browser
</div>
</>
);
};
With header
import PullToRefresh from "react-awesome-ptr";
import "react-awesome-ptr/dist/index.css";
export const WithHeader = () => {
const targetRef = useRef<HTMLDivElement>(null);
const [isRefreshing, setIsRefreshing] = useState(false);
const onRefresh = useCallback(() => {
setIsRefreshing(true);
setTimeout(() => {
setIsRefreshing(false);
}, 3000);
}, []);
return (
<>
<div
style={{
height: 60,
background: "cyan",
padding: 20,
position: "fixed",
top: 0,
width: "100%",
zIndex: 1,
}}
>
Header
</div>
<PullToRefresh
targetRef={targetRef}
onRefresh={onRefresh}
isRefreshing={isRefreshing}
originTop={100}
originMarginTop={100}
hasDefaultPullToRefreshPossibly
/>
<div
style={{ height: "100vh", background: "pink", padding: 20, marginTop: 100 }}
ref={targetRef}
>
Pull in a mobile browser
</div>
</>
);
};
Artificial bounce
import PullToRefresh from "react-awesome-ptr";
import "react-awesome-ptr/dist/index.css";
export const ArtificialBounce = () => {
const targetRef = useRef<HTMLDivElement>(null);
const [isRefreshing, setIsRefreshing] = useState(false);
const onRefresh = useCallback(() => {
setIsRefreshing(true);
setTimeout(() => {
setIsRefreshing(false);
}, 3000);
}, []);
return (
<>
<PullToRefresh
targetRef={targetRef}
onRefresh={onRefresh}
isRefreshing={isRefreshing}
isBounceNotSupported
/>
<div style={{ height: "100vh", background: "pink", padding: 20 }} ref={targetRef}>
Pull in a mobile browser (forced artificial bounce)
</div>
</>
);
};
CupertinoSpinner as Custom spinner
import PullToRefresh, { CupertinoSpinner } from "react-awesome-ptr";
import "react-awesome-ptr/dist/index.css";
export const CupertinoSpinnerAsCustomSpinner = () => {
const targetRef = useRef<HTMLDivElement>(null);
const [isRefreshing, setIsRefreshing] = useState(false);
const [progress, setProgress] = useState(0);
const [pullToRefreshState, setPullToRefreshState] = useState<PullToRefreshState>(
"idle",
);
const isTriggerReady = useMemo(() => {
return pullToRefreshState === "triggerReady";
}, [pullToRefreshState]);
const onRefresh = useCallback(() => {
setIsRefreshing(true);
setTimeout(() => {
setIsRefreshing(false);
}, 3000);
}, []);
const onPull = useCallback((progress: number) => {
setProgress(progress);
}, []);
const onChangeState = useCallback((state: PullToRefreshState) => {
setPullToRefreshState(state);
}, []);
const customSpinner = useMemo(() => {
return (
<CupertinoSpinner
progress={progress}
isRefreshing={isRefreshing}
isTriggerReady={isTriggerReady}
/>
);
}, [pullToRefreshState, progress, isRefreshing, isTriggerReady]);
return (
<>
<PullToRefresh
targetRef={targetRef}
onRefresh={onRefresh}
isRefreshing={isRefreshing}
onPull={onPull}
onChangeState={onChangeState}
customSpinner={customSpinner}
hasDefaultPullToRefreshPossibly
isOpacityChangeOnPullDisabled
isRotationSpinnerOnPullDisabled
completeDelay={200}
/>
<div style={{ height: "100vh", background: "pink", padding: 20 }} ref={targetRef}>
<p>Pull in a mobile browser (CupertinoSpinner as custom spinner)</p>
</div>
</>
);
};
Custom spinner
import PullToRefresh from "react-awesome-ptr";
import "react-awesome-ptr/dist/index.css";
export const CustomSpinner = () => {
const targetRef = useRef<HTMLDivElement>(null);
const [isRefreshing, setIsRefreshing] = useState(false);
const [progress, setProgress] = useState(0);
const [pullToRefreshState, setPullToRefreshState] = useState("idle");
const onRefresh = useCallback(() => {
setIsRefreshing(true);
setTimeout(() => {
setIsRefreshing(false);
}, 3000);
}, []);
const onPull = useCallback((progress: number) => {
setProgress(progress);
}, []);
const onChangeState = useCallback((state: PullToRefreshState) => {
setPullToRefreshState(state);
}, []);
const customSpinner = useMemo(() => {
return (
<div style={{ textAlign: "center", marginTop: 15 }}>
{pullToRefreshState === "triggerReady"
? "⬆️ Release"
: pullToRefreshState === "refreshing"
? "Refreshing..."
: pullToRefreshState === "complete"
? "Complete"
: `⬇️ Pull to refresh (${(progress * 100).toFixed()}%)`}
</div>
);
}, [pullToRefreshState, isRefreshing, progress]);
return (
<>
<PullToRefresh
targetRef={targetRef}
onRefresh={onRefresh}
isRefreshing={isRefreshing}
onPull={onPull}
onChangeState={onChangeState}
customSpinner={customSpinner}
completeDelay={500}
hasDefaultPullToRefreshPossibly
/>
<div style={{ height: "100vh", background: "pink", padding: 20 }} ref={targetRef}>
<p>Pull in a mobile browser (custom spinner)</p>
</div>
</>
);
};
Hidden spinner during refreshing
import PullToRefresh from "react-awesome-ptr";
import "react-awesome-ptr/dist/index.css";
export const HiddenSpinnerDuringRefreshing = () => {
const targetRef = useRef<HTMLDivElement>(null);
const [isRefreshing, setIsRefreshing] = useState(false);
const onRefresh = useCallback(() => {
setIsRefreshing(true);
setTimeout(() => {
setIsRefreshing(false);
}, 3000);
}, []);
return (
<>
<PullToRefresh
targetRef={targetRef}
onRefresh={onRefresh}
isRefreshing={isRefreshing}
isSpinnerHiddenDuringRefreshing
hasDefaultPullToRefreshPossibly
/>
<div style={{ height: "100vh", background: "pink", padding: 20 }} ref={targetRef}>
<p>Pull in a mobile browser (hidden spinner during refreshing)</p>
<p>{isRefreshing ? "Refreshing..." : ""}</p>
</div>
</>
);
};
Dark mode
import PullToRefresh from "react-awesome-ptr";
import "react-awesome-ptr/dist/index.css";
export const HiddenSpinnerDuringRefreshing = () => {
const targetRef = useRef<HTMLDivElement>(null);
const [isRefreshing, setIsRefreshing] = useState(false);
const onRefresh = useCallback(() => {
setIsRefreshing(true);
setTimeout(() => {
setIsRefreshing(false);
}, 3000);
}, []);
useEffect(() => {
const originBackgroundColor = document.body.style.backgroundColor;
document.body.style.backgroundColor = "#000";
return () => {
document.body.style.backgroundColor = originBackgroundColor;
};
}, []);
return (
<>
<PullToRefresh
targetRef={targetRef}
onRefresh={onRefresh}
isRefreshing={isRefreshing}
isDarkMode
hasDefaultPullToRefreshPossibly
/>
<div style={{ height: "100vh", background: "pink", padding: 20 }} ref={targetRef}>
Pull in a mobile browser (dark mode)
</div>
</>
);
};
Props
| name | type | required | default | description |
| ------------------------------- | :-----------------------------------: | :------: | :-----: | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| targetRef | React.RefObject<HTMLElement>
| O | | Target element to pull |
| originTop | number
| | 0
| Top of the target where pull-to-refresh starts based on clientRects |
| originMarginTop | number
| | 0
| Original margin of the target |
| triggerHeight | number
| | 80
| The height(distance) at which pull-to-refresh is triggered |
| progressHeight | number
| | 50
| Height to keep during refresh |
| onRefresh | VoidFunction
| O | | Callback to refresh |
| refreshDelay | number
| | 0
| If refresh time is too short to show spinner, use this prop to delay. |
| isRefreshing | boolean
| O | | Set true
during refresh. |
| spinnerSize | number
| | 32
| Size of spinner in pixel |
| tension | number
| | 0.8
| Value of artificial tension. Set under 1, 0 is the most powerful tension. (0.85 ~ 0.75 is appropriate) |
| isBounceSupported | boolean
| | | Set true
if native scroll bounce is supported not in iOS. |
| isBounceNotSupported | boolean
| | | Set true
if native scroll bounce is not supported in iOS. |
| customSpinner | React.ReactNode
| | | Custom spinner |
| onPull | (progress: number) => void
| | | Callback passing progress 0 to 1 as a param that is called when user is pulling |
| onRelease | VoidFunction
| | | Callback that is called when user releases target |
| onChangeState | (state: PullToRefreshState) => void
| | | Callback passing state idle
, pulling
, triggerReady
, refreshing
, complete
when state changes |
| completeDelay | number
| | 0
| Set milliseconds if you want to show complete message during complete
|
| isHiddenSpinnerDuringRefreshing | boolean
| | | Set true
if you have to hide spinner during refreshing |
| hideDelay | number
| | | Set milliseconds with the prop isHiddenSpinnerDuringRefreshing
as true
if you want to delay hiding spinner instead of hiding directly. |
| hasDefaultPullToRefreshPossibly | boolean
| | | Set true
if your service is possibly served in browsers that has default pull-to-refresh. |
| isDarkMode | boolean
| | | Set true
if default spinner needs to be shown above dark background |
| spinnerZIndex | number
| | -1
| |
| isDisabled | boolean
| | | Set true
to disable pull to refresh |
| isOpacityChangeOnPullDisabled | boolean
| | | Set true
if you don't want to change spinner's opacity on pull |
| isRotationSpinnerOnPullDisabled | boolean
| | | Set true
if you don't want to rotate spinner on pull |
Contributions
Contributions will be welcomed! Just make PRs to https://github.com/eunvanz/react-awesome-ptr.
Have some Github contributions?
You probably like my side project 👉 https://gitkemon.com/link/sl_68A
License
react-awesome-pull-to-refresh is released under the MIT license.