Проблема с черным экраном симулятора Xcode при закрытии конкретной модели - React Native ExpoIOS

Программируем под IOS
Ответить
Anonymous
 Проблема с черным экраном симулятора Xcode при закрытии конкретной модели - React Native Expo

Сообщение Anonymous »

Проблема

После успешного запроса, когда модальное окно закрывается с помощью setVisible(false), вот так появляется черный экран. Чтобы объяснить это более понятно: я открываю модальное окно с помощью этой кнопки, и после заполнения формы в модальном окне, когда я нажимаю кнопку отправки, появляется всплывающее окно загрузки, а затем сразу же появляется полный черный экран.
Связанная часть моего кода:
const mutation = useMutation({
mutationFn: createAffirmation,
onSuccess: async () => {
resetAffirmation("RESET_REQUEST", affirmationCtx);
setSelectedCategory("All");
setAffirmationText("");
setImage("");
setIsPrivate(true);
showSuccess("Affirmation created successfully!");
setVisible(false); // {
handleErrors(err, affirmationCtx);
},
});

const handlePress = () => {
const payload: AffirmationReqMode = {
mode: "manuel",
data: {
category: selectedCategory,
image: image,
isPublic: !isPrivate,
tags: [],
text: affirmationText,
},
};

const req = handleAffirmationReq(payload, affirmationCtx);

if (!req) {
return;
}

mutation.mutate(req);
};

Если я удалю setVisible(false), то черного экрана больше не будет, но почему?
Особенности
  • Если я закрою модальное окно с помощью крестовой кнопки, проблем не возникнет (кстати, кнопка с крестом закрывает модальное окно с помощью setVisible(false)).
  • Такой проблемы нет когда я пробую его на своем устройстве Android и на симуляторе Android (я не могу попробовать на устройстве iOS, потому что у меня нет iPhone, поэтому мне приходится использовать для этого симулятор xcode).
  • Существует еще один модальный модуль, который использует систему, аналогичную (или даже почти идентичную) той, которую использует этот конкретный модальный модуль, и это не вызывает никаких проблем.
  • В консоли expo cli ошибок нет.
Что я использую
  • Аппаратное обеспечение: Mac mini m4
  • ОС: macOS Tahoe 26.1
  • Симулятор: симулятор Xcode iPhone 16e и iPhone 17 pro (оба iOS 26.1)
  • Версия Xcode: версия 26.1.1 (17B100)
  • У меня телефон: Samsung Galaxy A23
  • Окружение: React-native (0.81.4), expo (54.0.12)
Полно моего кода
NewAffirmationForm.tsx (проблемный один)
import { ThinLoadingIcon } from "@/components/icons";
import { AppPopup } from "@/components/overlays";
import {
AppButton,
AppHeader,
AppPhotoInput,
AppPicker,
AppTextArea,
CloseButton,
ToggleCard,
} from "@/components/ui";
import { popupContent } from "@/content";
import { spacing } from "@/design-tokens";
import { AppTheme } from "@/design-tokens/colors";
import usePremium from "@/features/payment/hooks/usePremium";
import useImage from "@/hooks/useImage";
import useStyles from "@/hooks/useStyles";
import {
Affirmation,
AffirmationCategory,
AffirmationReq,
AffirmationReqMode,
AffirmationRes,
} from "@/types";
import { showSuccess } from "@/utils/toast";
import { FlashListRef } from "@shopify/flash-list";
import { useMutation } from "@tanstack/react-query";
import { AxiosError } from "axios";
import { router } from "expo-router";
import { RefObject, useState } from "react";
import { Modal, ScrollView, StyleSheet, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { createAffirmation } from "../api";
import { categories } from "../content";
import { useAffirmation } from "../hooks";
import { handleErrors } from "../utils/api";
import { resetAffirmation, setAffirmationPopup } from "../utils/dispatch";
import { handleAffirmationReq } from "../utils/query";
import AffirmationPopup from "./AffirmationPopup";

type props = {
visible: boolean;
setVisible: React.Dispatch;
affirmationsFeedRef: RefObject;
onClose: () => void;
};

const NewAffirmationForm = ({ visible, setVisible, onClose }: props) => {
const [selectedCategory, setSelectedCategory] =
useState("All");
const [affirmationText, setAffirmationText] = useState("");
const [image, setImage] = useState("");
const [isPrivate, setIsPrivate] = useState(true);

const insets = useSafeAreaInsets();
const { pickImage } = useImage();
const { styles, theme } = useStyles(makeStyles);
const affirmationCtx = useAffirmation();
const premium = usePremium();

const mutation = useMutation({
mutationFn: createAffirmation,
onSuccess: async () => {
resetAffirmation("RESET_REQUEST", affirmationCtx);
setSelectedCategory("All");
setAffirmationText("");
setImage("");
setIsPrivate(true);
showSuccess("Affirmation created successfully!");
setVisible(false);
},
onError: (err) => {
handleErrors(err, affirmationCtx);
},
});

const handlePress = () => {
const payload: AffirmationReqMode = {
mode: "manuel",
data: {
category: selectedCategory,
image: image,
isPublic: !isPrivate,
tags: [],
text: affirmationText,
},
};

const req = handleAffirmationReq(payload, affirmationCtx);

if (!req) {
return;
}

mutation.mutate(req);
};

const handlePressUploadImage = () => {
const isPremium = premium.data?.data.subscription.isPremium;

if (!isPremium) {
setAffirmationPopup(affirmationCtx, popupContent.payment.photo, () => {
setVisible(false);
router.push("/paywall");
});
return;
}

pickImage(setImage);
};

return (









{mutation.isPending ? (

) : (
"Create"
)}




{mutation.isPending && }

);
};

const makeStyles = (theme: AppTheme) =>
StyleSheet.create({
container: {
backgroundColor: theme.background,
paddingHorizontal: spacing["s-4"],
paddingBottom: spacing["s-4"],
gap: spacing["s-4"],
justifyContent: "space-between",
},
contentContainer: {
gap: spacing["s-4"],
flex: 1,
backgroundColor: theme.background,
},
});

export default NewAffirmationForm;

NormalEntry.tsx (еще одно похожее модальное окно (я упоминал), но не вызывающее черный экран, как показано выше.)
import { ThinLoadingIcon } from "@/components/icons";
import { AppButton, AppHeader, CloseButton } from "@/components/ui";
import { DayStreak } from "@/components/ui/DayStreak";
import { popupContent } from "@/content";
import { spacing } from "@/design-tokens";
import useTheme from "@/hooks/useTheme";
import { CreateEntryRes, EntryReq, GratitudeEntry } from "@/types";
import { getClientTimeZone } from "@/utils/date";
import { buildDeviceString } from "@/utils/metaData";
import { getTagsFromString } from "@/utils/string";
import { showSuccess } from "@/utils/toast";
import { FlashListRef } from "@shopify/flash-list";
import { useMutation } from "@tanstack/react-query";
import { AxiosError } from "axios";
import { RefObject } from "react";
import { Modal, ScrollView, View } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { createEntry } from "../api";
import { TodaysAffirmation } from "../components";
import { useEntry } from "../hooks";
import {
AddEntrySection,
EntryEmoteSection,
EntryPhotoSection,
EntryToggleSection,
} from "../sections";
import { handleErrors } from "../utils/api";
import {
resetEntry,
setEntryError,
setEntryPopup,
setEntryStatus,
} from "../utils/dispatch";
import EntryPopup from "./EntryPopup";

const NormalEntry = ({
visible,
onClose,
setVisible,
entriesFeedRef,
}: {
visible: boolean;
setVisible: React.Dispatch;
entriesFeedRef: RefObject;
onClose: () => void;
}) => {
const insets = useSafeAreaInsets();
const { theme } = useTheme();
const entryCtx = useEntry();
const mutation = useMutation({
mutationFn: createEntry,
onSuccess: (res) => {
showSuccess("Entry created successfully!");
resetEntry("RESET_REQUEST", entryCtx);

if (res) {
if (!res.data) return;

setVisible(false);
entriesFeedRef.current?.scrollToIndex({ index: 0, animated: true });
}
},
onError: (err) => {
handleErrors(err, entryCtx);
},
});

const handlePress = async () => {
const ctxReq = entryCtx.state.values.request;

const req: EntryReq = {
gratefulFor: ctxReq.gratefulFor,
mood: ctxReq.mood,
photo: ctxReq.photo,
isPrivate: ctxReq.isPrivate,
tags: getTagsFromString(ctxReq.gratefulFor.join(", ")),
metaData: {
deviceInfo: buildDeviceString(),
timeZone: getClientTimeZone(),
},
};

if (!Array.isArray(req.gratefulFor) || req.gratefulFor.length === 0) {
setEntryError(entryCtx, {
...entryCtx.state.errors,
gratefulFor: ["add at least one"],
});

setEntryPopup(entryCtx, popupContent.createEntry.missingText);
setEntryStatus(entryCtx, "fail");

return;
}

if (!req.mood) {
setEntryError(entryCtx, {
...entryCtx.state.errors,
mood: ["please select a mood"],
});

setEntryPopup(entryCtx, popupContent.createEntry.missingEmote);
setEntryStatus(entryCtx, "fail");

return;
}

mutation.mutate(req);
};

return (



















{mutation.isPending ? (

) : (
"Sent"
)}






);
};

export default NormalEntry;

AffirmationScreen.tsx (где я вызвал компонент NewAffirmationForm. Родитель этого компонента.)
import { ThinLoadingIcon } from "@/components/icons";
import { AppPopup } from "@/components/overlays";
import { AppHeader, AppText } from "@/components/ui";
import { AFFIRMATIONS_FEED_QUERY_KEY } from "@/constants";
import { spacing } from "@/design-tokens";
import { AppTheme } from "@/design-tokens/colors";
import { fetchAffirmations } from "@/features/affirmation/api";
import { AffirmationCard } from "@/features/affirmation/components";
import { useAffirmation } from "@/features/affirmation/hooks";
import {
AffirmationPopup,
NewAffirmationForm,
} from "@/features/affirmation/overlay";
import {
AffirmationCollection,
AffirmationSlider,
EntryUnlockSection,
} from "@/features/affirmation/sections";
import {
closeAffirmationPopup,
resetAffirmation,
} from "@/features/affirmation/utils/dispatch";
import usePremium from "@/features/payment/hooks/usePremium";
import useStyles from "@/hooks/useStyles";
import useTheme from "@/hooks/useTheme";
import { Affirmation, AffirmationCategory, AffirmationsRes } from "@/types";
import { FlashList, FlashListRef } from "@shopify/flash-list";
import { InfiniteData, useInfiniteQuery } from "@tanstack/react-query";
import { AxiosError } from "axios";
import { useMemo, useRef, useState } from "react";
import {
RefreshControl,
StyleSheet,
TouchableOpacity,
View,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";

const AffirmationScreen = () => {
const [showNewAffirmationForm, setShowNewAffirmationForm] =
useState(false);
const [selectedFilter, setSelectedFilter] =
useState("All");

const { styles } = useStyles(makeStyles);
const { theme } = useTheme();
const affirmationCtx = useAffirmation();
const premium = usePremium();

const affirmationsFeedRef = useRef(null);

const isPremium = premium.data?.data.subscription.isPremium;
const PAGE_SIZE = isPremium ? 5 : 3;

const {
data,
error,
isLoading,
isRefetching,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
refetch,
} = useInfiniteQuery<
AffirmationsRes,
AxiosError,
InfiniteData,
[typeof AFFIRMATIONS_FEED_QUERY_KEY, AffirmationCategory],
number
>({
queryKey: [AFFIRMATIONS_FEED_QUERY_KEY, selectedFilter],
initialPageParam: 1,
queryFn: ({ pageParam, queryKey, signal }) => {
const [, filter] = queryKey;
return fetchAffirmations({
params: { page: pageParam, limit: PAGE_SIZE, category: filter },
opts: { signal },
});
},
getNextPageParam: (lastPage, _allPages, lastPageParam) => {
if (!isPremium) return;
const pg = lastPage.data.pagination;
return pg.hasNext ? lastPageParam + 1 : undefined;
},
});

const affirmations: Affirmation[] = useMemo(() => {
return data?.pages.flatMap((p) => p.data.affirmations) ?? [];
}, [data]);

if (isLoading) {
return (



);
}

if (error) {
return (

Something went wrong: {error.message}
refetch()}>
Refetch (temp)


);
}

const handleClose = () => {
setShowNewAffirmationForm(false);
resetAffirmation("RESET_REQUEST", affirmationCtx);
closeAffirmationPopup(affirmationCtx);
};

return (

item?._id}
renderItem={({ item }) => (


Подробнее здесь: https://stackoverflow.com/questions/798 ... d-react-na
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «IOS»