Как в react-router-dom реализовать загрузчик (loader), который не будет блокировать рендеринг страницы?

В этом роутере есть возможность назначить loader для каждого роута — функцию, отвечающую за получение данных до загрузки компонента. По умолчанию выполнение этого loader'a блокирует рендеринг компонента до тех пор, пока не поступит ответ от сервера. <br/><br/> <i>Существуют ли способы отключить блокировку рендеринга и дождаться получения данных уже в загруженном компоненте?</i> <br/><br/> P.S. Это имеет критическое значение, поскольку я использую ленивую загрузку в роутере.
  • 23 октября 2024 г. 0:12
Ответы на вопрос 2
В `react-router-dom` (версии 6 и выше) механизмы загрузки (loader) встроены так, что они блокируют рендеринг компонентов до получения данных. Однако вы можете обойти эту блокировку, используя альтернативный подход для получения данных внутри самого компонента.

Вот шаги, которые помогут вам достичь этого:

1. **Удалите loader из вашего маршрута**: Уберите функцию loader из ваших маршрутов в `react-router-dom`.

2. **Используйте хук `useEffect` в компоненте**: Вместо того чтобы использовать loader на уровне маршрута, вы можете получить данные внутри вашего компонента с использованием хука `useEffect`. В этом случае, во время первого рендера, ваш компонент будет рендериться даже до того, как данные будут получены, и вы сможете использовать состояние для отображения загрузчика.

3. **Обработайте состояние загрузки и ошибки**: Создайте состояния для отслеживания загрузки и ошибок, чтобы управлять тем, что отображается на экране.

Вот пример реализации:

```javascript
import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';

const fetchData = async () => {
    // Здесь ваша логика получения данных
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
        throw new Error('Ошибка загрузки данных');
    }
    return response.json();
};

const MyComponent = () => {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        const getData = async () => {
            try {
                setLoading(true);
                const result = await fetchData();
                setData(result);
            } catch (err) {
                setError(err.message);
            } finally {
                setLoading(false);
            }
        };

        getData();
    }, []);

    if (loading) {
        return <div>Загрузка...</div>;
    }

    if (error) {
        return <div>Ошибка: {error}</div>;
    }

    return (
        <div>
            <h1>Данные загружены</h1>
            <pre>{JSON.stringify(data, null, 2)}</pre>
        </div>
    );
};

const App = () => (
    <Router>
        <Routes>
            <Route path="/" element={<MyComponent />} />
            {/* Другие маршруты */}
        </Routes>
    </Router>
);

export default App;
```

В этом примере:

- `MyComponent` загружает данные при помощи `fetchData` внутри `useEffect`.
- Во время загрузки отображается "Загрузка…".
- Если происходит ошибка, она отображается.
- После успешного получения данных отображается контент.

Таким образом, вы можете избежать блокировки рендеринга страницы, но при этом обрабатывать отправку и получение данных внутри вашего компонента.
Сам не пользовался, но по докам вроде никаких магических параметров не вижу. 

Вот тебе тогда вспомогательные функции которые сделают то, что тебе надо:
import { json, useLoaderData } from "react-router-dom";
import { useEffect } from "react";

export function eagerLoader(loader) {
  return (...args) => {
    const promise = loader(...args)
      .then(res => res instanceof Response ? res.json() : res );
    return json({ promise }, { status: 200 });
  }
}

export function useEagerLoaderData(defaultData) {
  const [data, setData] = useState(defaultData);
  const promise = useLoaderData()?.promise;

  useEffect(() => {
    promise?.then(setData);
  }, [promise]);

  return data;
}
let route = {
  path: "...",
  loader: eagerLoader(({ request }) => ...),
  lazy: () => import("./Comp"),
};

const Comp = () => {
  const data = useEagerLoaderData([]);

  //...
}

Код не проверял, но думаю логика очевидна.:)

P.S. defaultData мб стоит вынести в обёртку eagerLoader, хз, тут сам смотри свои юзкейсы.:)
Похожие вопросы