sanManjiro
@sanManjiro

Почему данные не подгружаются в форму?

У меня есть localStorage, откуда подгружаются данные во время загрузки страницы:
// NOTE: Объект с данными формы
  const [dataForm, setDataForm] = useState<IDataForm>({
    lastName: "",
    firstName: "",
    middleName: "",
    dateOfBirth: "",
    numberPhone: "",
    email: "",
    amount: 1000,
    term: 5,
    checkbox: false,
  });

  // NOTE: Обновление данных формы из LocalStorage
  useEffect(() => {
    const storedData = getStoredData<IDataForm>(StorageKeys.GENERAL_INFO);
    if (storedData) {
      setDataForm(storedData);
    }
  }, []);

, проблема данного кода в том, что если в конце useEffect вывести dataForm, то данных там не будет, т.к. useEffect загружается последним, при этом если использовать такую запись:
const [dataForm, setDataForm] = useState<IDataForm>(() => {
  if (typeof window !== 'undefined') {
    const storedData = getStoredData<IDataForm>(StorageKeys.GENERAL_INFO);
    return storedData || {
      lastName: '',
      firstName: '',
      middleName: '',
      dateOfBirth: '',
      numberPhone: '',
      email: '',
      amount: 1000,
      term: 5,
      checkbox: false
    };
  }
  return {};
});

, то данные подгружаются в вставляются в форму, но я получаю ошибку
Uncaught Error: Text content does not match server-rendered HTML.
See more info here:
, как мне обойти рассинхрон сервера и клиента и при этому загрузить все данные и подставить в форму? Полные вариант компонента: https://pastebin.com/0PmTYhhE
  • Вопрос задан
  • 38 просмотров
Пригласить эксперта
Ответы на вопрос 3
GreyCrew
@GreyCrew
Full-stack developer
Дело в том, что реакт рендерит разное содержимое на сервере и на клиенте. Это часто случается при использовании localStorage во время серверного рендеринга или в условиях, когда код выполняется до того, как реакт синхронизирует состояние с содержимым сервера.

Попробуй использовать флаг, у тебя должно получится что то типа такого:
const [dataForm, setDataForm] = useState<IDataForm>({
  lastName: "",
  firstName: "",
  middleName: "",
  dateOfBirth: "",
  numberPhone: "",
  email: "",
  amount: 1000,
  term: 5,
  checkbox: false,
});

// NOTE: Флаг для отслеживания, был ли компонент смонтирован на клиенте
const [isClient, setIsClient] = useState(false);

useEffect(() => {
  setIsClient(true);
}, []);

useEffect(() => {
  if (isClient) {
    const storedData = getStoredData<IDataForm>(StorageKeys.GENERAL_INFO);
    if (storedData) {
      setDataForm(storedData);
    }
  }
}, [isClient]);

Соответственно код будет работать для use client
Ответ написан
sanManjiro
@sanManjiro Автор вопроса
Хотел использовать Redux для этого, установил и настроил но из-за typescript не было компонента PersistGate, ну или он просто не определял типы, но я нашел для себя заглушку этого, не знаю правда настолько это правильный подход:
useEffect(() => {
    if (dataForm) {
      setValue("amount", dataForm.amount);
      setValue("term", dataForm.term);
      setValue("lastName", dataForm.lastName);
      setValue("firstName", dataForm.firstName);
      setValue("middleName", dataForm.middleName);
      setValue("numberDate", dataForm.dateOfBirth);
      setValue("numberPhone", dataForm.numberPhone);
      setValue("email", dataForm.email);
      setValue("checkbox", dataForm.checkbox);
    }
  }, [dataForm, setValue]);

, я добавил просто второй useEffect после первого, где storedData и просто руками записываю значения в форму, это работает как надо и ошибки нету, но я думаю но КОСТЫЛЬ ли это и можно ли считать тему закрытой.
Ответ написан
@ludvigruno
Фулстэк-разработчик
У тебя проблема связана с гидратацией — это когда серверный HTML отличается от клиентского из-за того, что данные из localStorage подгружаются уже после рендера.

Вот как это можно обойти:

1. Вариант с useEffect остаётся нормальным. Ты просто сначала задаёшь дефолтное состояние, а потом в useEffect подтягиваешь данные из localStorage. Да, данные сначала будут пустыми, но зато не будет ошибки гидратации.

const [dataForm, setDataForm] = useState<IDataForm>({
  lastName: '',
  firstName: '',
  middleName: '',
  dateOfBirth: '',
  numberPhone: '',
  email: '',
  amount: 1000,
  term: 5,
  checkbox: false,
});

useEffect(() => {
  const storedData = getStoredData<IDataForm>(StorageKeys.GENERAL_INFO);
  if (storedData) {
    setDataForm(storedData);
  }
}, []);

Если критично, чтобы форма не показывалась до загрузки данных, то просто добавь условие:

if (!dataForm) {
  return null; // можно спиннер или что-то такое показать
}

2. Если хочешь, чтобы данные подгружались сразу без лишнего рендера, то данные надо либо уже отдавать с сервера (например, через getServerSideProps, если у тебя Next.js), либо хардкодить их в начальное состояние, но это не всегда удобно.

3. Ещё вариант — проверить на клиентской стороне. Например:

const [isClient, setIsClient] = useState(false);

useEffect(() => {
  setIsClient(true);
}, []);

if (!isClient) return null; // или показывай что-то временное

return (
  <YourFormComponent dataForm={dataForm} />
);

Этот способ тоже рабочий и позволяет избежать ошибки гидратации. В общем, всё сводится к тому, чтобы подгружать данные только после того, как React знает, что он на клиенте, и не пытаться смешивать логику клиента и сервера.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы