react-native-yet-another-stopwatch-timer
v1.0.1
Published
Highly extensible stopwatch, timer, counter component for React Native
Maintainers
Readme
react-native-yet-another-stopwatch-timer
Features
A fully extensible stopwatch, timer component for React Native.
- Uses react-native-reanimated and react-native-worklets to make performant component updates
- Allows for custom timing functions, precision, group / digit rendering, states and transitions
Installation
npm install --save react-native-yet-another-stopwatch-timerThis library uses react-native-reanimated and react-native-worklets listed as peer dependencies so you have to provide them if your project does not already use them:
npm install --save react-native-reanimated react-native-workletsYes, since v7 npm install automatically installs peerDependencies, however I got 'react-native-worklets doesn't seem to be initialized' error loop in a fresh project until installed peerDependencies manually.
Add react-native-worklets plugin to your babel.config.js as stated in reanimated installation guide.
Minimal supported versions of peer dependencies are defined by react-native-reanimated compatibility table and react-native-worklets compatibility table.
Usage
Check out example project for most of use cases:
| File | Example description | | :---- | :----------- | | Stopwatch | controlling the stopwatch, providing style for digits, callback after every transition | | Timer | controlling the timer, setting initial value for countdown, providing style for digits, callback after every transition | | Derived | controlling the timer, setting initial value for countdown, providing style for digits, custom render function | | Intervals | controlling the stopwatch, providing style for digits, using counter value in handler, using callback after transition success, using Static renderer for other component to preserve same style |
Or check minimal examples to copy-paste:
Minimal Stopwatch example
import React, { useRef, useCallback } from 'react';
import { View, TouchableOpacity, Text } from 'react-native';
import { Stopwatch, StopwatchTransitions } from 'react-native-yet-another-stopwatch-timer';
const Component = () => {
const timerRef = useRef(null);
// use timerRef to call transitionTo property to switch states
const run = useCallback(() => timerRef.current?.transitionTo({ name: StopwatchTransitions.Run }), [ timerRef ]);
// use onBeforeTransition, onAfterTransition callback to access counter on state change
const pause = useCallback(() => timerRef.current?.transitionTo({ name: StopwatchTransitions.Pause, onAfterTransition: console.log }), [ timerRef ]);
return (
<View style={{ flex: 1 }}>
<TouchableOpacity onPress={run}><Text>Run</Text></TouchableOpacity>
<TouchableOpacity onPress={pause}><Text>Pause</Text></TouchableOpacity>
<Stopwatch timerRef={timerRef} />
</View>
);
};Minimal Timer usage example
import React, { useState, useRef, useCallback } from 'react';
import { View, TouchableOpacity, Text } from 'react-native';
import { Timer, TimerTransitions, TimerStates } from 'react-native-yet-another-stopwatch-timer';
const Component = ({ initialCounterValue = 5000 }) => {
const [ laps, setLaps ] = useState(0);
const timerRef = useRef(null);
// use timerRef to call transitionTo property to switch states, set transition name to one of StopwatchTransitions, counterValue if you want to change it outside of timingHandler
const run = useCallback(() => timerRef.current?.transitionTo({ name: TimerTransitions.Run, counterValue: initialCounterValue }), [ timerRef, initialCounterValue ]);
const onAfterTransition = useCallback((_, { name }, { nextState }) => {
if (name === TimerTransitions.Stop && nextState === TimerStates.Stopped) setLaps((prevLaps) => prevLaps + 1);
}, [ setLaps ]);
return (
<View style={{ flex: 1 }}>
<TouchableOpacity onPress={run}><Text>Run</Text></TouchableOpacity>
<Timer timerRef={timerRef} onAfterTransition={onAfterTransition} initialCounterValue={initialCounterValue} />
<Text>Laps: {laps}</Text>
</View>
);
};API
Autogenerated documentation is here.
Q&A
How to show less or more places? How to render differently, use other digit changing animations?
Provide your own render property and declare needed reanimated derived values for counter, that gets updated by timingHandler each timingInterval by timingInterval, by default counter gets incremented by 100 every 100 ms. Check example Derived.
How to get better timing precision?
setTimeout and setInterval guarantee that callback will be called not sooner than timeout ms. For precise timing you can either implement a self adjusting timer, that compensates for varying timeout call times, or use Date.now(), or if you just need to accurately capture when run, pause or stop events occur, capture precise time in global onBeforeTransition/onAfterTransition handlers, or for individual transitions in transitionHandler.
Getting started with customisation
The module exports Counter, Stopwatch and Timer components, state and transition names.
Counter serves as base component that only provides means of calling transition function and registers timing handler.
Stopwatch is implemented by providing default values for Counter:
initialStatefor the state machinetimingHandlerto increment the counter at least every timingInterval by timingIntervalremoveTimingto clean the timeout when the component is removedtransitionHandlerthat returns an object withnextStateproperty based on transition and current staterenderfunction that returns a React component provided counter and style arguments
Timer features are achieved by providing other timing function to Stopwatch that decrements the counter and issues a transition to stopped state when it reaches zero.
Development
In order to develop the application or build android .apk from the sources one should:
- Clone this repository
- Navigate to parent directory and install dev dependencies with
npm cifor lintingnpm run lintand typescript typecheckingnpm run typecheck. - Navigate to example folder:
cd example - Install example project dependencies
npm ci, since library has only peer dependencies - Run Metro bundler with
npm run start - Connect physical device or an emulator via adb, like this (tested with mEMU):
adb connect 127.0.0.1:21503adb reverse tcp:8081 tcp:8081
- Build and watch with
npm run-android, changes from src directory are picked automatically because of example metro and babel configurations.
Note: example project is configured in a way, that may cause issues if you save anything as a regular dependency in parent library
Contributions
PR are always welcome!
