@umituz/react-native-bottom-sheet 1.1.6 → 1.1.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-bottom-sheet",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
4
4
  "description": "Modern, performant bottom sheets for React Native with preset configurations, keyboard handling, and smooth animations",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
package/src/index.ts CHANGED
@@ -108,3 +108,8 @@ export {
108
108
 
109
109
  // Re-export BottomSheetModalProvider for convenience
110
110
  export { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
111
+
112
+ // Safe provider that ensures Reanimated is ready before rendering
113
+ export {
114
+ SafeBottomSheetModalProvider,
115
+ } from './presentation/components/SafeBottomSheetModalProvider';
@@ -156,66 +156,7 @@ export const BottomSheet = forwardRef<BottomSheetRef, BottomSheetProps>(
156
156
  const sheetRef = React.useRef<GorhomBottomSheet>(null);
157
157
  const [isMounted, setIsMounted] = useState(false);
158
158
 
159
- // Ensure component is mounted after Reanimated is ready
160
- // This prevents layoutState.get errors during initial render
161
- // @gorhom/bottom-sheet uses useAnimatedDetents which accesses layoutState.get
162
- // during initialization, so we need to wait for Reanimated to be fully ready
163
- useEffect(() => {
164
- // Use a longer delay to ensure Reanimated is fully initialized
165
- // This is critical because @gorhom/bottom-sheet's internal hooks
166
- // access layoutState.get immediately when the component renders
167
- const timer = setTimeout(() => {
168
- // Use multiple animation frames to ensure Reanimated worklets are ready
169
- requestAnimationFrame(() => {
170
- requestAnimationFrame(() => {
171
- requestAnimationFrame(() => {
172
- setIsMounted(true);
173
- });
174
- });
175
- });
176
- }, 300); // Increased delay to 300ms to ensure Reanimated is fully initialized
177
-
178
- return () => {
179
- clearTimeout(timer);
180
- };
181
- }, []);
182
-
183
- // Expose ref methods
184
- React.useImperativeHandle(ref, () => ({
185
- snapToIndex: (index: number) => {
186
- if (isMounted) {
187
- sheetRef.current?.snapToIndex(index);
188
- }
189
- },
190
- snapToPosition: (position: string | number) => {
191
- if (isMounted) {
192
- sheetRef.current?.snapToPosition(position);
193
- }
194
- },
195
- expand: () => {
196
- if (isMounted) {
197
- sheetRef.current?.expand();
198
- }
199
- },
200
- collapse: () => {
201
- if (isMounted) {
202
- sheetRef.current?.collapse();
203
- }
204
- },
205
- close: () => {
206
- if (isMounted) {
207
- sheetRef.current?.close();
208
- }
209
- },
210
- }));
211
-
212
- // Don't compute config or callbacks until mounted to prevent early hook execution
213
- // This ensures @gorhom/bottom-sheet's internal hooks don't run before Reanimated is ready
214
- if (!isMounted) {
215
- return null;
216
- }
217
-
218
- // Get configuration from preset or custom
159
+ // Get configuration from preset or custom (must be before useImperativeHandle)
219
160
  const config: BottomSheetConfig = useMemo(() => {
220
161
  if (customSnapPoints) {
221
162
  return BottomSheetUtils.createConfig({
@@ -244,7 +185,31 @@ export const BottomSheet = forwardRef<BottomSheetRef, BottomSheetProps>(
244
185
  enableDynamicSizing,
245
186
  ]);
246
187
 
247
- // Render backdrop component
188
+ // Ensure component is mounted after Reanimated is ready
189
+ // This prevents layoutState.get errors during initial render
190
+ // @gorhom/bottom-sheet uses useAnimatedDetents which accesses layoutState.get
191
+ // during initialization, so we need to wait for Reanimated to be fully ready
192
+ useEffect(() => {
193
+ // Use a longer delay to ensure Reanimated is fully initialized
194
+ // This is critical because @gorhom/bottom-sheet's internal hooks
195
+ // access layoutState.get immediately when the component renders
196
+ const timer = setTimeout(() => {
197
+ // Use multiple animation frames to ensure Reanimated worklets are ready
198
+ requestAnimationFrame(() => {
199
+ requestAnimationFrame(() => {
200
+ requestAnimationFrame(() => {
201
+ setIsMounted(true);
202
+ });
203
+ });
204
+ });
205
+ }, 300); // Increased delay to 300ms to ensure Reanimated is fully initialized
206
+
207
+ return () => {
208
+ clearTimeout(timer);
209
+ };
210
+ }, []);
211
+
212
+ // Render backdrop component (must be before early return to maintain hook order)
248
213
  const renderBackdrop = useCallback(
249
214
  (props: BottomSheetBackdropProps) =>
250
215
  enableBackdrop ? (
@@ -259,7 +224,7 @@ export const BottomSheet = forwardRef<BottomSheetRef, BottomSheetProps>(
259
224
  [enableBackdrop, config.backdropAppearsOnIndex, config.backdropDisappearsOnIndex]
260
225
  );
261
226
 
262
- // Handle sheet changes
227
+ // Handle sheet changes (must be before early return to maintain hook order)
263
228
  const handleSheetChange = useCallback(
264
229
  (index: number) => {
265
230
  onChange?.(index);
@@ -270,6 +235,42 @@ export const BottomSheet = forwardRef<BottomSheetRef, BottomSheetProps>(
270
235
  [onChange, onClose]
271
236
  );
272
237
 
238
+ // Expose ref methods (must be before early return to maintain hook order)
239
+ React.useImperativeHandle(ref, () => ({
240
+ snapToIndex: (index: number) => {
241
+ if (isMounted) {
242
+ sheetRef.current?.snapToIndex(index);
243
+ }
244
+ },
245
+ snapToPosition: (position: string | number) => {
246
+ if (isMounted) {
247
+ sheetRef.current?.snapToPosition(position);
248
+ }
249
+ },
250
+ expand: () => {
251
+ if (isMounted) {
252
+ sheetRef.current?.expand();
253
+ }
254
+ },
255
+ collapse: () => {
256
+ if (isMounted) {
257
+ sheetRef.current?.collapse();
258
+ }
259
+ },
260
+ close: () => {
261
+ if (isMounted) {
262
+ sheetRef.current?.close();
263
+ }
264
+ },
265
+ }));
266
+
267
+ // Don't render until mounted to prevent early hook execution
268
+ // This ensures @gorhom/bottom-sheet's internal hooks don't run before Reanimated is ready
269
+ // IMPORTANT: All hooks must be called before this early return to maintain hook order
270
+ if (!isMounted) {
271
+ return null;
272
+ }
273
+
273
274
  return (
274
275
  <GorhomBottomSheet
275
276
  ref={sheetRef}
@@ -211,7 +211,33 @@ export const BottomSheetModal = forwardRef<BottomSheetModalRef, BottomSheetModal
211
211
  };
212
212
  }, []);
213
213
 
214
- // Expose ref methods
214
+ // Render backdrop component (must be before early return to maintain hook order)
215
+ const renderBackdrop = useCallback(
216
+ (props: BottomSheetBackdropProps) =>
217
+ enableBackdrop ? (
218
+ <BottomSheetBackdrop
219
+ {...props}
220
+ appearsOnIndex={config.backdropAppearsOnIndex ?? 0}
221
+ disappearsOnIndex={config.backdropDisappearsOnIndex ?? -1}
222
+ opacity={0.5}
223
+ pressBehavior="close"
224
+ />
225
+ ) : null,
226
+ [enableBackdrop, config.backdropAppearsOnIndex, config.backdropDisappearsOnIndex]
227
+ );
228
+
229
+ // Handle sheet changes (must be before early return to maintain hook order)
230
+ const handleSheetChange = useCallback(
231
+ (index: number) => {
232
+ onChange?.(index);
233
+ if (index === -1) {
234
+ onDismiss?.();
235
+ }
236
+ },
237
+ [onChange, onDismiss]
238
+ );
239
+
240
+ // Expose ref methods (must be before early return to maintain hook order)
215
241
  React.useImperativeHandle(ref, () => ({
216
242
  present: () => {
217
243
  if (isMounted && modalRef.current) {
@@ -248,36 +274,11 @@ export const BottomSheetModal = forwardRef<BottomSheetModalRef, BottomSheetModal
248
274
 
249
275
  // Don't render until mounted to prevent early hook execution
250
276
  // This ensures @gorhom/bottom-sheet's internal hooks don't run before Reanimated is ready
277
+ // IMPORTANT: All hooks must be called before this early return to maintain hook order
251
278
  if (!isMounted) {
252
279
  return null;
253
280
  }
254
281
 
255
- // Render backdrop component
256
- const renderBackdrop = useCallback(
257
- (props: BottomSheetBackdropProps) =>
258
- enableBackdrop ? (
259
- <BottomSheetBackdrop
260
- {...props}
261
- appearsOnIndex={config.backdropAppearsOnIndex ?? 0}
262
- disappearsOnIndex={config.backdropDisappearsOnIndex ?? -1}
263
- opacity={0.5}
264
- pressBehavior="close"
265
- />
266
- ) : null,
267
- [enableBackdrop, config.backdropAppearsOnIndex, config.backdropDisappearsOnIndex]
268
- );
269
-
270
- // Handle sheet changes
271
- const handleSheetChange = useCallback(
272
- (index: number) => {
273
- onChange?.(index);
274
- if (index === -1) {
275
- onDismiss?.();
276
- }
277
- },
278
- [onChange, onDismiss]
279
- );
280
-
281
282
  return (
282
283
  <GorhomBottomSheetModal
283
284
  ref={modalRef}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * SafeBottomSheetModalProvider
3
+ *
4
+ * Enhanced BottomSheetModalProvider that ensures Reanimated is fully
5
+ * initialized before rendering. This prevents "containerLayoutState.get
6
+ * is not a function" errors that occur when @gorhom/bottom-sheet's
7
+ * internal hooks access Reanimated's layoutState before it's ready.
8
+ *
9
+ * Usage:
10
+ * ```tsx
11
+ * import { SafeBottomSheetModalProvider } from '@umituz/react-native-bottom-sheet';
12
+ *
13
+ * <SafeBottomSheetModalProvider>
14
+ * <App />
15
+ * </SafeBottomSheetModalProvider>
16
+ * ```
17
+ */
18
+
19
+ import React, { useState, useEffect } from 'react';
20
+ import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
21
+
22
+ interface SafeBottomSheetModalProviderProps {
23
+ children: React.ReactNode;
24
+ }
25
+
26
+ /**
27
+ * SafeBottomSheetModalProvider
28
+ *
29
+ * Delays rendering of BottomSheetModalProvider until Reanimated is ready.
30
+ * This prevents layoutState.get errors during initial render.
31
+ */
32
+ export const SafeBottomSheetModalProvider: React.FC<SafeBottomSheetModalProviderProps> = ({
33
+ children,
34
+ }) => {
35
+ const [isReanimatedReady, setIsReanimatedReady] = useState(false);
36
+
37
+ useEffect(() => {
38
+ // Wait for Reanimated to be fully initialized
39
+ // @gorhom/bottom-sheet uses useAnimatedDetents which accesses layoutState.get
40
+ // during initialization, so we need to wait for Reanimated to be fully ready
41
+ const timer = setTimeout(() => {
42
+ // Use multiple animation frames to ensure Reanimated worklets are ready
43
+ requestAnimationFrame(() => {
44
+ requestAnimationFrame(() => {
45
+ requestAnimationFrame(() => {
46
+ setIsReanimatedReady(true);
47
+ });
48
+ });
49
+ });
50
+ }, 300); // Delay to ensure Reanimated is fully initialized
51
+
52
+ return () => {
53
+ clearTimeout(timer);
54
+ };
55
+ }, []);
56
+
57
+ // Don't render BottomSheetModalProvider until Reanimated is ready
58
+ if (!isReanimatedReady) {
59
+ return <>{children}</>;
60
+ }
61
+
62
+ return <BottomSheetModalProvider>{children}</BottomSheetModalProvider>;
63
+ };
64
+