1.0.63 • Published 2 years ago

react-awesome-ptr v1.0.63

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

react-awesome-ptr

npm version npm bundle size npm type definitions npm GitHub

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

<img src="https://user-images.githubusercontent.com/17351661/134833837-ad712f07-0a97-43ef-9117-90e214f65032.gif" width="300" />

CupertinoSpinner example

<img src="https://user-images.githubusercontent.com/17351661/145903534-bb3397b8-73a1-4342-9ff1-6b8d57536a70.gif" width="300" />

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

nametyperequireddefaultdescription
targetRefReact.RefObject<HTMLElement>OTarget element to pull
originTopnumber0Top of the target where pull-to-refresh starts based on clientRects
originMarginTopnumber0Original margin of the target
triggerHeightnumber80The height(distance) at which pull-to-refresh is triggered
progressHeightnumber50Height to keep during refresh
onRefreshVoidFunctionOCallback to refresh
refreshDelaynumber0If refresh time is too short to show spinner, use this prop to delay.
isRefreshingbooleanOSet true during refresh.
spinnerSizenumber32Size of spinner in pixel
tensionnumber0.8Value of artificial tension. Set under 1, 0 is the most powerful tension. (0.85 ~ 0.75 is appropriate)
isBounceSupportedbooleanSet true if native scroll bounce is supported not in iOS.
isBounceNotSupportedbooleanSet true if native scroll bounce is not supported in iOS.
customSpinnerReact.ReactNodeCustom spinner
onPull(progress: number) => voidCallback passing progress 0 to 1 as a param that is called when user is pulling
onReleaseVoidFunctionCallback that is called when user releases target
onChangeState(state: PullToRefreshState) => voidCallback passing state idle, pulling, triggerReady, refreshing, complete when state changes
completeDelaynumber0Set milliseconds if you want to show complete message during complete
isHiddenSpinnerDuringRefreshingbooleanSet true if you have to hide spinner during refreshing
hideDelaynumberSet milliseconds with the prop isHiddenSpinnerDuringRefreshing as true if you want to delay hiding spinner instead of hiding directly.
hasDefaultPullToRefreshPossiblybooleanSet true if your service is possibly served in browsers that has default pull-to-refresh.
isDarkModebooleanSet true if default spinner needs to be shown above dark background
spinnerZIndexnumber-1
isDisabledbooleanSet true to disable pull to refresh
isOpacityChangeOnPullDisabledbooleanSet true if you don't want to change spinner's opacity on pull
isRotationSpinnerOnPullDisabledbooleanSet 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.

1.0.62

2 years ago

1.0.61

2 years ago

1.0.63

2 years ago

1.0.60

2 years ago

1.0.59

2 years ago

1.0.48

2 years ago

1.0.47

2 years ago

1.0.46

2 years ago

1.0.49

2 years ago

1.0.51

2 years ago

1.0.50

2 years ago

1.0.55

2 years ago

1.0.54

2 years ago

1.0.53

2 years ago

1.0.58

2 years ago

1.0.57

2 years ago

1.0.56

2 years ago

1.0.44

2 years ago

1.0.45

2 years ago

1.0.39

3 years ago

1.0.40

3 years ago

1.0.43

2 years ago

1.0.42

2 years ago

1.0.41

3 years ago

1.0.37

3 years ago

1.0.38

3 years ago

1.0.33

3 years ago

1.0.32

3 years ago

1.0.31

3 years ago

1.0.30

3 years ago

1.0.36

3 years ago

1.0.35

3 years ago

1.0.34

3 years ago

1.0.29

3 years ago

1.0.25

3 years ago

1.0.28

3 years ago

1.0.27

3 years ago

1.0.22

3 years ago

1.0.21

3 years ago

1.0.24

3 years ago

1.0.23

3 years ago

1.0.19

3 years ago

1.0.18

3 years ago

1.0.17

3 years ago

1.0.16

3 years ago

1.0.20

3 years ago

1.0.15

3 years ago

1.0.14

3 years ago

1.0.13

3 years ago

1.0.12

3 years ago

1.0.9

3 years ago

1.0.8

3 years ago

1.0.7

3 years ago

1.0.11

3 years ago

1.0.10

3 years ago

1.0.2

3 years ago

1.0.1

3 years ago

1.0.0

3 years ago

1.0.6

3 years ago

1.0.5

3 years ago

1.0.4

3 years ago

1.0.3

3 years ago

0.3.2

3 years ago

0.3.1

3 years ago

0.3.0

3 years ago

0.2.5

3 years ago

0.2.4

3 years ago

0.2.3

3 years ago

0.2.2

3 years ago

0.2.1

3 years ago

0.2.0

3 years ago

0.1.1

3 years ago

0.1.0

3 years ago

0.0.1

3 years ago