Вопрос или проблема
Как правильно использовать компонент Trans (или альтернативу), чтобы достичь своей цели и часть переведенного предложения могла загружаться асинхронно?
Я думал, что это может сработать:
"use client";
import { Suspense } from "react";
import { Trans } from "react-i18next"; // https://react.i18next.com/latest/trans-component
const mockedFetch = (delay: number) => {
return new Promise<string>((resolve) => {
setTimeout(() => {
const last4DigitsOfCard = "3423";
resolve(last4DigitsOfCard);
}, delay);
});
};
function PersonalizedNumber() {
// TODO: Удалите все моки. Замените реальными запросами к серверу.
const delay = 2_000;
// Мы здесь выбрасываем промис, чтобы Suspense мог поймать его и показать загрузчик:
const sentencePromise = mockedFetch(delay);
// `Suspense` автоматически обработает это как состояние загрузки:
return sentencePromise;
}
function Loading() {
return <div>Загрузка...</div>;
}
function Last4DigitsOfCard() {
return (
<Suspense fallback={<Loading />}>
<PersonalizedNumber />
</Suspense>
);
}
export function YourPrepaidCardEndingInLastFour({
locale,
}: {
locale: string;
}) {
return (
<Trans
locale={locale}
i18nKey="yourPrepaidCardIsReady"
components={{
last4DigitsOfCard: <Last4DigitsOfCard />,
}}
/>
);
}
Ключ перевода:
"yourPrepaidCardIsReady": "Ваша предоплаченная карта, завершающаяся на <last4DigitsOfCard/>, активна и готова к использованию."
Но я получаю:
Unhandled Runtime Error
Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
Это, похоже, работает.
"use client";
import { useEffect, useState } from "react";
import { Trans } from "react-i18next"; // https://react.i18next.com/latest/trans-component
// TODO: Удалите все моки. Замените реальными запросами к серверу.
const delay = 2_000;
const sampleLast4DigitsOfCard = "3423";
const mockedFetch = (delayMs: number) => {
return new Promise<string>((resolve) => {
setTimeout(() => {
resolve(sampleLast4DigitsOfCard);
}, delayMs);
});
};
function Loading() {
return (
<span style={{ border: "1px solid black", padding: "2px" }}>
Загрузка...
</span>
);
}
function Last4DigitsOfCardSimple() {
return sampleLast4DigitsOfCard;
}
function Last4DigitsOfCardAsync() {
const [value, setValue] = useState<string | null>(null);
useEffect(() => {
async function main() {
const lastFourDigits = await mockedFetch(delay);
setValue(lastFourDigits);
}
main();
}, []);
return value ?? <Loading />;
}
export function YourPrepaidCardEndingInLastFour({
locale,
}: {
locale: string;
}) {
return (
<div>
<div>
Асинхронный:{" "}
<Trans
locale={locale}
i18nKey="yourPrepaidCardIsReady"
components={{
last4DigitsOfCard: <Last4DigitsOfCardAsync />,
}}
/>
</div>
<br />
<br />
<div>
Простой (синхронный):{" "}
<Trans
locale={locale}
i18nKey="yourPrepaidCardIsReady"
components={{
last4DigitsOfCard: <Last4DigitsOfCardSimple />,
}}
/>
</div>
</div>
);
}
Но он использует useEffect
вместо Suspense
.
Мне интересно узнать, как это сделать с помощью Suspense
.
Ответ или решение
Использование React Suspense в сочетании с компонентом Trans
из библиотеки react-i18next
может быть немного сложным, особенно если вы хотите загружать часть перевода асинхронно. Ваша цель заключается в том, чтобы использовать контейнер Suspense
для асинхронного получения данных, таких как последние четыре цифры кредитной карты, и отобразить их при загрузке.
К сожалению, прямое использование Promise
в компоненте React не работает, так как React не ожидает Promise
и пытается отрисовать его как обычный элемент. Вместо этого, вам нужно будет слегка изменить подход.
Вот как можно реализовать вашу задачу с использованием React Suspense
:
Шаг 1: Создайте отдельный компонент для загрузки
Вместо того, чтобы возвращать Promise
напрямую в компоненте, создайте компонент, который будет использовать Suspense
и имитировать асинхронную загрузку.
"использовать клиент";
import { Suspense } from "react";
import { Trans } from "react-i18next";
const mockedFetch = (delay) => {
return new Promise((resolve) => {
setTimeout(() => {
const last4DigitsOfCard = "3423";
resolve(last4DigitsOfCard);
}, delay);
});
};
// Создаем обертку для использования с Suspense
function UseSuspenseForLast4Digits() {
const last4DigitsPromise = mockedFetch(2000);
if (last4DigitsPromise && typeof last4DigitsPromise.then === 'function') {
throw last4DigitsPromise; // Бросаем промис, чтобы Suspense мог обработать его
}
return last4DigitsPromise; // Замените на фактическое получение данных
}
function Loading() {
return <div>Loading...</div>;
}
function Last4DigitsOfCard() {
return (
<Suspense fallback={<Loading />}>
<UseSuspenseForLast4Digits />
</Suspense>
);
}
export function YourPrepaidCardEndingInLastFour({ locale }) {
return (
<Trans
locale={locale}
i18nKey="yourPrepaidCardIsReady"
components={{
last4DigitsOfCard: <Last4DigitsOfCard />,
}}
/>
);
}
Шаг 2: Переопределите компонент Suspense
Теперь вы можете использовать данный подход в вашем компоненте Last4DigitsOfCard
, чтобы обеспечить плавную загрузку данных с использованием Suspense
.
Шаг 3: Убедитесь, что ваш перевод корректен
Сперва вам нужно настроить ваш файл переводов. Вот пример перевода в JSON:
{
"yourPrepaidCardIsReady": "Ваша предоплаченная карта, заканчивающаяся на <last4DigitsOfCard />, активна и готова к использованию."
}
Заключение
Теперь компонент будет работать с использованием React Suspense
, обеспечивая асинхронную загрузку данных без прямого использования useEffect
. Это позволяет более аккуратно обрабатывать состояние загрузки и отображать компонент, который будет вызывать новый контент текущего времени. Таким образом, вы сможете добиться желаемого поведения выполнения кода.