react-native-toast-message 2.0.2 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -9,6 +9,28 @@ Headers are one of:
9
9
 
10
10
  - `Added`, `Changed`, `Removed`, `Fixed` or `Breaking`.
11
11
 
12
+ ## [2.1.1]
13
+
14
+ ### Fixed
15
+
16
+ - Tapping a View / Button behind the Toast is not possible ([b9d191e](https://github.com/calintamas/react-native-toast-message/commit/b9d191e1dcc4d331fd5159ca9aabd54d7d0ecb97)) (fixes [#282](https://github.com/calintamas/react-native-toast-message/issues/282))
17
+
18
+ ## [2.1.0]
19
+
20
+ ### Fixed
21
+
22
+ - After a Toast is shown within a Modal, the main instance outside of Modal doesn't work anymore ([#293](https://github.com/calintamas/react-native-toast-message/pull/293))
23
+ - A previously set timer is not cleared when `autoHide` changes from `true` to `false`. This can make a newly shown Toast auto hide (even if it was shown with `autoHide: false`) ([#294](https://github.com/calintamas/react-native-toast-message/pull/294))
24
+
25
+ Big thanks go to [jstheoriginal](https://github.com/jstheoriginal) for all the work on fixing the two issues above 🙌.
26
+
27
+ - Flexbox not working for setting Toast width or alignment ([3400f00](https://github.com/calintamas/react-native-toast-message/commit/3400f0074116f5acb37ca2eb696ea50b0c669ddc))
28
+
29
+ ### Changed
30
+
31
+ - `BaseToastProps` style types allows passing an array of styles now `style={[styles.one, styles.two]}` ([#243](https://github.com/calintamas/react-native-toast-message/pull/243) was ported to v2)
32
+ - Peer deps no longer require a min version ([e91ed21](https://github.com/calintamas/react-native-toast-message/commit/e91ed21d277d7348b674834765147be752b6abfb))
33
+
12
34
  ## [2.0.2]
13
35
 
14
36
  ### Fixed
package/README.md CHANGED
@@ -33,3 +33,9 @@ Animated toast message component for React Native.
33
33
  ## License
34
34
 
35
35
  MIT
36
+
37
+ ## Support
38
+
39
+ Not a requirement in any way, but if this package helped you (or your company) and you feel like supporting my future work:
40
+
41
+ <a href="https://buymeacoffee.com/calintamas"><img src="https://user-images.githubusercontent.com/9104454/143683074-69dc6a53-3e54-4a2c-8dcf-c1e27448a2f4.png" alt="Buy me a coffee" width="150px" /></a>
package/lib/src/Toast.js CHANGED
@@ -7,21 +7,83 @@ const ToastRoot = React.forwardRef((props, ref) => {
7
7
  const { show, hide, isVisible, options, data } = useToast({
8
8
  defaultOptions
9
9
  });
10
- React.useImperativeHandle(ref, () => ({
10
+ // This must use useCallback to ensure the ref doesn't get set to null and then a new ref every render.
11
+ React.useImperativeHandle(ref, React.useCallback(() => ({
11
12
  show,
12
13
  hide
13
- }));
14
+ }), [hide, show]));
14
15
  return (<ToastUI isVisible={isVisible} options={options} data={data} hide={hide} show={show} config={config}/>);
15
16
  });
16
- const toastRef = React.createRef();
17
+ let refs = [];
18
+ /**
19
+ * Adds a ref to the end of the array, which will be used to show the toasts until its ref becomes null.
20
+ *
21
+ * @param newRef the new ref, which must be stable for the life of the Toast instance.
22
+ */
23
+ function addNewRef(newRef) {
24
+ refs.push({
25
+ current: newRef
26
+ });
27
+ }
28
+ /**
29
+ * Removes the passed in ref from the file-level refs array using a strict equality check.
30
+ *
31
+ * @param oldRef the exact ref object to remove from the refs array.
32
+ */
33
+ function removeOldRef(oldRef) {
34
+ refs = refs.filter((r) => r.current !== oldRef);
35
+ }
17
36
  export function Toast(props) {
37
+ const toastRef = React.useRef(null);
38
+ /*
39
+ This must use `useCallback` to ensure the ref doesn't get set to null and then a new ref every render.
40
+ Failure to do so will cause whichever Toast *renders or re-renders* last to be the instance that is used,
41
+ rather than being the Toast that was *mounted* last.
42
+ */
43
+ const setRef = React.useCallback((ref) => {
44
+ // Since we know there's a ref, we'll update `refs` to use it.
45
+ if (ref) {
46
+ // store the ref in this toast instance to be able to remove it from the array later when the ref becomes null.
47
+ toastRef.current = ref;
48
+ addNewRef(ref);
49
+ }
50
+ else {
51
+ // remove the this toast's ref, wherever it is in the array.
52
+ removeOldRef(toastRef.current);
53
+ }
54
+ }, []);
18
55
  return (<LoggerProvider enableLogs={false}>
19
- <ToastRoot ref={toastRef} {...props}/>
56
+ <ToastRoot ref={setRef} {...props}/>
20
57
  </LoggerProvider>);
21
58
  }
59
+ /**
60
+ * Get the active Toast instance `ref`, by priority.
61
+ * The "highest" Toast in the `View` hierarchy has the highest priority.
62
+ *
63
+ * For example, a Toast inside a `Modal`, would have had its ref set later than a Toast inside App's Root.
64
+ * Therefore, the library knows that it is currently visible on top of the App's Root
65
+ * and will thus use the `Modal`'s Toast when showing/hiding.
66
+ *
67
+ * ```js
68
+ * <>
69
+ * <Toast />
70
+ * <Modal>
71
+ * <Toast />
72
+ * </Modal>
73
+ * </>
74
+ * ```
75
+ */
76
+ function getRef() {
77
+ const reversePriority = [...refs].reverse();
78
+ const activeRef = reversePriority.find((ref) => ref?.current !== null);
79
+ if (!activeRef) {
80
+ return null;
81
+ }
82
+ return activeRef.current;
83
+ }
22
84
  Toast.show = (params) => {
23
- toastRef.current?.show(params);
85
+ getRef()?.show(params);
24
86
  };
25
87
  Toast.hide = (params) => {
26
- toastRef.current?.hide(params);
88
+ getRef()?.hide(params);
27
89
  };
@@ -72,7 +72,10 @@ export function AnimatedContainer({ children, isVisible, position, topOffset, bo
72
72
  const newAnimationValue = isVisible ? 1 : 0;
73
73
  animate(newAnimationValue);
74
74
  }, [animate, isVisible]);
75
- return (<Animated.View testID={getTestId('AnimatedContainer')} onLayout={computeViewDimensions} style={[styles.base, styles[position], animationStyles]} {...panResponder.panHandlers}>
75
+ return (<Animated.View testID={getTestId('AnimatedContainer')} onLayout={computeViewDimensions} style={[styles.base, styles[position], animationStyles]}
76
+ // This container View is never the target of touch events but its subviews can be.
77
+ // By doing this, tapping buttons behind the Toast is allowed
78
+ pointerEvents='box-none' {...panResponder.panHandlers}>
76
79
  {children}
77
80
  </Animated.View>);
78
81
  }
@@ -1,9 +1,10 @@
1
1
  export declare const styles: {
2
2
  base: {
3
3
  position: "absolute";
4
+ left: number;
5
+ right: number;
4
6
  alignItems: "center";
5
7
  justifyContent: "center";
6
- alignSelf: "center";
7
8
  };
8
9
  top: {
9
10
  top: number;
@@ -2,9 +2,10 @@ import { StyleSheet } from 'react-native';
2
2
  export const styles = StyleSheet.create({
3
3
  base: {
4
4
  position: 'absolute',
5
+ left: 0,
6
+ right: 0,
5
7
  alignItems: 'center',
6
- justifyContent: 'center',
7
- alignSelf: 'center'
8
+ justifyContent: 'center'
8
9
  },
9
10
  top: {
10
11
  top: 0
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { TextStyle, TouchableOpacityProps, ViewStyle } from 'react-native';
2
+ import { StyleProp, TextProps, TextStyle, TouchableOpacityProps, ViewProps, ViewStyle } from 'react-native';
3
3
  export declare type ReactChildren = React.ReactNode;
4
4
  export declare type ToastType = string;
5
5
  export declare type ToastPosition = 'top' | 'bottom';
@@ -74,16 +74,16 @@ export declare type BaseToastProps = {
74
74
  text2?: string;
75
75
  onPress?: () => void;
76
76
  activeOpacity?: number;
77
- style?: ViewStyle;
77
+ style?: StyleProp<ViewStyle>;
78
78
  touchableContainerProps?: TouchableOpacityProps;
79
- contentContainerStyle?: ViewStyle;
80
- contentContainerProps?: ViewStyle;
81
- text1Style?: TextStyle;
79
+ contentContainerStyle?: StyleProp<ViewStyle>;
80
+ contentContainerProps?: ViewProps;
81
+ text1Style?: StyleProp<TextStyle>;
82
82
  text1NumberOfLines?: number;
83
- text1Props?: TextStyle;
84
- text2Style?: TextStyle;
83
+ text1Props?: TextProps;
84
+ text2Style?: StyleProp<TextStyle>;
85
85
  text2NumberOfLines?: number;
86
- text2Props?: TextStyle;
86
+ text2Props?: TextProps;
87
87
  renderLeadingIcon?: () => React.ReactNode;
88
88
  renderTrailingIcon?: () => React.ReactNode;
89
89
  };
@@ -101,6 +101,10 @@ export declare type ToastConfigParams<Props> = {
101
101
  export declare type ToastConfig = {
102
102
  [key: string]: (params: ToastConfigParams<any>) => React.ReactNode;
103
103
  };
104
+ export declare type ToastRef = {
105
+ show: (params: ToastShowParams) => void;
106
+ hide: (params: ToastHideParams) => void;
107
+ };
104
108
  /**
105
109
  * `props` that can be set on the Toast instance.
106
110
  * They act as defaults for all Toasts that are shown.
@@ -41,9 +41,6 @@ export function useToast({ defaultOptions }) {
41
41
  const show = React.useCallback((params) => {
42
42
  log(`Showing with params: ${JSON.stringify(params)}`);
43
43
  const { text1 = DEFAULT_DATA.text1, text2 = DEFAULT_DATA.text2, type = initialOptions.type, position = initialOptions.position, autoHide = initialOptions.autoHide, visibilityTime = initialOptions.visibilityTime, topOffset = initialOptions.topOffset, bottomOffset = initialOptions.bottomOffset, keyboardOffset = initialOptions.keyboardOffset, onShow = initialOptions.onShow, onHide = initialOptions.onHide, onPress = initialOptions.onPress, props = initialOptions.props } = params;
44
- // TODO: validate input
45
- // TODO: use a queue when Toast is already visible
46
- setIsVisible(true);
47
44
  setData({
48
45
  text1,
49
46
  text2
@@ -61,14 +58,22 @@ export function useToast({ defaultOptions }) {
61
58
  onPress,
62
59
  props
63
60
  }));
61
+ // TODO: validate input
62
+ // TODO: use a queue when Toast is already visible
63
+ setIsVisible(true);
64
64
  onShow();
65
65
  }, [initialOptions, log]);
66
66
  React.useEffect(() => {
67
67
  const { autoHide } = options;
68
- if (isVisible && autoHide) {
69
- startTimer();
68
+ if (isVisible) {
69
+ if (autoHide) {
70
+ startTimer();
71
+ }
72
+ else {
73
+ clearTimer();
74
+ }
70
75
  }
71
- }, [isVisible, options, startTimer]);
76
+ }, [isVisible, options, startTimer, clearTimer]);
72
77
  return {
73
78
  isVisible,
74
79
  data,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-toast-message",
3
- "version": "2.0.2",
3
+ "version": "2.1.1",
4
4
  "description": "Toast message component for React Native",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -21,7 +21,9 @@
21
21
  "prettier": "./node_modules/.bin/prettier --write",
22
22
  "lint": "./node_modules/.bin/eslint --fix",
23
23
  "lint-staged": "./node_modules/.bin/lint-staged",
24
- "test": "./node_modules/.bin/jest"
24
+ "test": "./node_modules/.bin/jest",
25
+ "yalc:push": "yarn build && yalc publish --push",
26
+ "quality": "yarn lint && tsc --noEmit"
25
27
  },
26
28
  "author": "Calin Tamas <calintamas2@gmail.com>",
27
29
  "license": "MIT",
@@ -32,20 +34,21 @@
32
34
  "@testing-library/react-native": "^8.0.0",
33
35
  "@types/jest": "^27.0.1",
34
36
  "@types/react-native": "^0.66.2",
35
- "eslint-config-backpacker-react-ts": "^0.1.0",
37
+ "eslint-config-backpacker-react-ts": "^0.2.0",
36
38
  "husky": "^7.0.2",
37
39
  "import-sort-style-module": "^6.0.0",
38
40
  "jest": "^27.1.1",
39
- "lint-staged": "^11.1.2",
41
+ "lint-staged": "^12.1.2",
40
42
  "metro-react-native-babel-preset": "^0.66.2",
41
43
  "prettier": "^2.4.1",
42
44
  "prettier-plugin-import-sort": "^0.0.7",
43
45
  "react-test-renderer": "^17.0.2",
44
- "typescript": "^4.4.3"
46
+ "typescript": "^4.4.3",
47
+ "yalc": "^1.0.0-pre.53"
45
48
  },
46
49
  "peerDependencies": {
47
- "react": ">=17.0.2",
48
- "react-native": ">=0.64.2"
50
+ "react": "*",
51
+ "react-native": "*"
49
52
  },
50
53
  "importSort": {
51
54
  ".js, .jsx, .ts, .tsx": {