Прощайте, useState и useEffect: революция в React

Многие разработчики продолжают использовать хуки useState и useEffect для обновления состояний, но мне такой подход не нравится. Проблема заключается в том, что компонент одновременно монтируется, перемонтируется и размонтируется, что приводит к неожиданному поведению. В результате при записи в консоль результат может быть повторен три раза.

Назначение хука useLoaderData

Хук useLoaderData  —  это пользовательский хук в React, который помогает загружать данные в компонент. Он упрощает получение данных из API и выполнение асинхронных операций.

Используя хук useLoaderData, вы предоставляете ему функцию, которая возвращает промис. Этот промис представляет собой асинхронную операцию, в результате которой будут получены необходимые данные. Как только промис разрешится, данные станут доступны компоненту.

Хук useLoaderData обрабатывает состояние загрузки, поэтому вам не нужно вручную отслеживать, загружаются ли еще данные или их загрузка уже закончена. Он предоставляет удобный способ доступа к данным, а также позволяет устранять возможные ошибки, которые могут возникнуть при загрузке данных.

Хук useLoaderData позволяет сохранить чистоту и организованность кода компонента, отделяя логику загрузки данных от остальных задач компонента. Это позволяет легко получать данные и управлять ими более удобным для новичков способом.

Зачем нужен useLoaderHook?

useLoaderHook из библиотеки React Router помогает достичь той же функциональности с минимальными усилиями. Вот лишь несколько причин, почему его стоит использовать.

  • Управление состоянием загрузки. Загрузчики (Loaders) берут на себя обработку состояния загрузки, обеспечивая четкое представление о том, когда данные будут получены. Это помогает управлять спиннерами загрузки, индикаторами прогресса и любыми другими UI-элементами, связанными с загрузкой данных.
  • Обработка ошибок. Загрузчики часто включают механизмы обработки ошибок, позволяющие обрабатывать и отображать неполадки, возникающие в процессе загрузки данных. Они обеспечивают стандартизированный способ обработки ошибок, что упрощает реализацию последовательной обработки ошибок в приложении.
  • Разделение задач. Загрузчики позволяют отделить логику загрузки данных от других аспектов компонента. Это улучшает организованность и сопровождаемость кода, поскольку позволяет сосредоточиться на конкретных задачах, не смешивая их.

Как это работает

Надеюсь, вы хорошо знаете, как функционирует React Router 6. В противном случае ознакомьтесь с документацией по ссылке.

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

Обсудим этот фрагмент.

import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom"
import HomeComponent from "./home"
import AboutCompoent from "./about"
function App () {
<BrowserRouter>
<Routes>
<Route path='/' element={<Outlet />}>
<Route index element={<HomeComponent /> } />
<Route path='about' element={<AboutComponent/> } />
</Route>
</Routes>
</BrowserRouter>
};
export default App;

Здесь создана традиционная система маршрутизации с использованием импорта из React Router.

Что тут происходит?

BrowserRouter из библиотеки React Router создает массив объектов из дочерних элементов Routes. Приведенный ниже фрагмент наглядно демонстрирует, как это происходит.

BrowserRouter([
{
path: '/',
element: <HomeComponent />,
children: []
},
{
path: '/about',
element: <AboutComponent/>,
children: []
}
])

Если они являются вложенными элементами route, то дочерний route добавляется к дочернему ключу в родительском route.

Да, таким образом сохраняется рекурсивность.

Однако этот метод нельзя применить для хука loaderData. Приходится делать небольшой рефакторинг. Все выглядит примерно так (рекомендую ознакомиться с документацией по React Router для получения дополнительной информации):

import { 
createBrowserRouter,
createRoutesFromElements,
RouterProvider,
Route,
Outlet
} from "react-router-dom"

import HomeComponent from "./home"
import AboutComponent from "./about"

function App() {
const browserRoutes = createBrowserRouter(createRoutesFromElements(
<Route path='/' element={<Outlet />}>
<Route index element={<HomeComponent /> } />
<Route path='about' element={<AboutComponent /> } />
</Route>
))

return (
<RouterProvider router={browserRoutes} />
);
}

Импортируем createBrowserRouter, createRoutesFromElement и RouterProvider.

Затем инициализируем переменную browserRoutes, которая будет служить объектом, подлежащим рендерингу. Обратите внимание: функция createRoutesFromElements вызывается внутри функции createBrowserRouter. Это связано с необходимостью парсинга или преобразования Routes в объект: функция createRoutesFromElements, как следует из ее названия, помогает в этом. Наконец, возвращаем RouterProvider со значением новой browserRouter. Теперь посмотрим, что бы мы делали, если бы не использовали функцию createRoutesFromElements.

createBrowserRouter([
{
path: '/',
element: <HomeComponent />,
children: []
},
{
path: '/about',
element: <AboutComponent/>,
children: []
}])

Я не большой поклонник этого метода, так как Route может стать вложенным, и в какой-то момент это приведет к путанице. Лучше все делать очень просто.

Ознакомление с функциями загрузчиков

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

Допустим, нам нужно получать данные из конечной точки и отображать их в компоненте homeComponent. Большинство разработчиков поступают следующим образом: инициализируют состояние и обновляют его в хуке useEffect. Приведенный ниже фрагмент наглядно демонстрирует это.

import { useState } from 'react'

const HomeComponent = () => {
const [data, setData] = useState([]);

useEffect(async () => {
const request = await fetch('http://localhost:3004/file');
if(!request.ok) throw new Error('Failed to fetch data')
const item= await request.json()
setData(item)
}, [])

return (
<section>
{ data.length > 0 ? data.map((foundData) => (
<div key={foundData.id}>
<strong>{foundData.name}</strong>
</div>
)) : <p>Data currently unavailable</p>}
</section>
)
}
export default HomeComponent

Здесь очень много строк, поэтому хотелось бы все немного упростить и, возможно, повторно использовать одну и ту же функцию.

Чтобы использовать загрузчик, необходимо определить функцию загрузчика. Функции загрузчиков подобны пользовательским хукам.

Кроме того, тут соглашение об именовании функции не имеет значения, поскольку ее можно назвать как угодно. В приведенном ниже фрагменте кода создадим базовую функцию загрузчика, которая будет получать данные из API, как показано в приведенном выше фрагменте:

export async function LoaderFunction () {
const request = await fetch('http://localhost:3004/file');
if (!request.ok) throw new Error ('Failed to fetch item')
const item = await response.json();
return item;
};

Теперь необходимо импортировать функцию загрузчика в компонент, в котором обрабатываются Routes. После настройки системы Route с помощью createBrowserRouter и createRouteFromElements должен появиться доступ к свойству loader. В качестве значения в него следует передать созданную функцию LoaderFunction.

В приведенном ниже фрагменте кода это наглядно показано.

import { 
createBrowserRouter,
createRoutesFromElements,
RouterProvider,
Route,
Outlet
} from "react-router-dom"
import HomeComponent from "./home"
import AboutComponent from "./about"
import { LoaderFunction as HomeLoader} from "./loader"

function App() {
const browserRoutes = createBrowserRouter(createRoutesFromElements(
<Route path='/' element={<Outlet />}>
<Route index element={<HomeComponent /> }
loader={HomeLoader}/>
<Route path='about' element={<AboutComponent /> } />
</Route>
))

return (
<RouterProvider router={browserRoutes} />
);
}

После этого можно получить доступ к данным, возвращаемым функцией загрузчика, используя хук useLoaderData из React Router в компоненте HomeComponent.

Понять, как это происходит, поможет приведенный ниже фрагмент кода.

import { useLoaderData } from "react-router-dom"

const HomeComponent = () => {
const data = useLoaderData();

return (
<section>
{data.map((foundData) => (
<div key={foundData.id}>
<strong>{foundData.name}</strong>
</div>
))}
</section>
)
}
export default HomeComponent

Посмотрите, как мы очистили HomeComponent. Обратите внимание, что мы избавились от охранной функции, которая проверяет, не являются ли данные null. Дело в том, что React Router заставляет загружать данные, когда url/путь становится активным. Таким образом, он создает необходимые запросы еще до того, как компонент будет смонтирован!

Мы обеспечили условия только для результативного пути. А если передадим несуществующую конечную точку? В этом случае не стоит паниковать, поскольку React Router также позволяет передавать компоненты в другое свойство, называемое errorElement. Оно предназначено специально для ошибок, как и ErrorBoundaries. Посмотрим, как это работает в приведенном ниже фрагменте.

import { 
createBrowserRouter,
createRoutesFromElements,
RouterProvider,
Route,
Outlet
} from "react-router-dom"
import HomeComponent from "./home"
import AboutComponent from "./about"
import { LoaderFunction as HomeLoader} from "./loader"

function App() {
const browserRoutes = createBrowserRouter(createRoutesFromElements(
<Route path='/' element={<Outlet />}>
<Route index element={<HomeComponent /> }
loader={HomeLoader} errorElement={<h1>An Error occured</h1>}/>
<Route path='about' element={<AboutComponent /> } />
</Route>
))

return (
<RouterProvider router={browserRoutes} />
);
}

Здесь просто использован тег заголовка, чтобы показать ошибку. Рекомендуется использовать компонент, чтобы получить доступ к хуку useRouteError. Так как происходит предварительное получение данных перед монтированием компонента, состояние загрузки становится неважным, поскольку хук может либо получить данные, либо вернуть сообщение об ошибке, которое вы передадите в качестве значения свойству errorElement.

Вот и все основные сведения о выполнении запросов с использованием API-уровня данных.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Emmanuel Odii: Bye-bye useState & useEffect: Revolutionizing React Development!

Предыдущая статьяРисуем Дораэмона с помощью Python
Следующая статьяПринципы SOLID в Kotlin