Utils
Пакет с хуками и хелперами для разработки React-компонентов: controlled/uncontrolled состояние, дебаунс, свайпы, модалки, персист данных, работа с пропами и SSR.
Оформление (тема, бренд, плотность, цветовая схема) переехало в
@cloud-ru/ds-theme— см. Theme и паттерн Оформление.
Установка
pnpm add @cloud-ru/ds-utils
Примеры использования
useValueControl
Поддержка controlled/uncontrolled: один интерфейс для value/defaultValue и onChange.
import { useValueControl } from '@cloud-ru/ds-utils';
function Toggle({ value, defaultValue, onChange }) {
const [open, setOpen] = useValueControl({ value, defaultValue, onChange });
return (
<button type="button" onClick={() => setOpen(!open)}>
{open ? 'Закрыть' : 'Открыть'}
</button>
);
}
useDebounce
Возвращает стабильный коллбек, который вызывается с задержкой после последнего вызова.
import { useDebounce } from '@cloud-ru/ds-utils';
function Search() {
const handleSearch = useDebounce(() => {
console.log('Поиск по запросу...');
}, 300);
return <input onChange={handleSearch} />;
}
useEventHandler
Возвращает обработчик с неизменной ссылкой: внутри всегда вызывается актуальная функция, без лишних ре-рендеров дочерних компонентов.
import { useEventHandler } from '@cloud-ru/ds-utils';
function List({ items, onItemClick }) {
const handleClick = useEventHandler(onItemClick);
return items.map((item) => (
<div key={item.id} onClick={handleClick} role="presentation">
{item.name}
</div>
));
}
useLayoutEffect (SSR-безопасный)
Из пакета экспортируется useLayoutEffect: в браузере это обычный useLayoutEffect, в SSR — useEffect.
import { useRef } from 'react';
import { useLayoutEffect } from '@cloud-ru/ds-utils';
function Measure() {
const ref = useRef(null);
useLayoutEffect(() => {
if (ref.current) console.log(ref.current.getBoundingClientRect());
}, []);
return <div ref={ref}>...</div>;
}
useDynamicList
Делит список на видимые и скрытые элементы по ширине контейнера.
import { useRef } from 'react';
import { useDynamicList } from '@cloud-ru/ds-utils';
function Tabs({ items }) {
const containerRef = useRef(null);
const { visibleItems, hiddenItems } = useDynamicList({
items,
parentContainerRef: containerRef,
maxVisibleItems: 5,
});
return (
<div ref={containerRef}>
{visibleItems.map((item) => (
<span key={item.id}>{item.label}</span>
))}
{hiddenItems.length > 0 && <span>…</span>}
</div>
);
}
useSwipeable
Обработка свайпов (на базе react-swipeable).
import { useSwipeable } from '@cloud-ru/ds-utils';
function SwipeCard() {
const handlers = useSwipeable({
onSwipedLeft: () => console.log('свайп влево'),
onSwipedRight: () => console.log('свайп вправо'),
availableDirections: ['LEFT', 'RIGHT'],
});
return <div {...handlers}>Свайпни меня</div>;
}
useModalOpenState
Подключает к модалке закрытие по кнопке «Назад» (popstate) и по CloseWatcher.
import { useModalOpenState } from '@cloud-ru/ds-utils';
function Modal({ open, onClose }) {
useModalOpenState(open, onClose, {
closeOnPopstate: true,
closeByCloseWatcher: true,
});
return open ? <div role="dialog">...</div> : null;
}
usePopstateSubscription
Подписка на событие popstate.
import { usePopstateSubscription } from '@cloud-ru/ds-utils';
function SyncWithHistory() {
usePopstateSubscription((e) => {
console.log('Навигация назад/вперёд', e.state);
}, true);
return null;
}
useDataPersist
Чтение и запись состояния в localStorage и в query-параметрах URL.
import { useState } from 'react';
import { useDataPersist } from '@cloud-ru/ds-utils';
const options = {
queryKey: 'filter',
localStorageKey: 'my-filter',
validateData: (v): v is { query: string } => typeof v?.query === 'string',
};
function FilterState() {
const { getDefaultData, setDataToStorages } = useDataPersist({ options });
const [filter, setFilter] = useState(getDefaultData ?? { query: '' });
const save = () => setDataToStorages(filter);
return <input value={filter.query} onChange={(e) => setFilter({ query: e.target.value })} onBlur={save} />;
}
excludeSupportProps, extractSupportProps, extractDataTestProps
import { excludeSupportProps, extractSupportProps, extractDataTestProps } from '@cloud-ru/ds-utils';
const props = {
'data-test-id': 'submit-btn',
'aria-label': 'Отправить',
onClick: () => {},
className: 'btn',
};
const restProps = excludeSupportProps(props);
const supportProps = extractSupportProps(props);
const dataTestProps = extractDataTestProps(props);
isBrowser
import { isBrowser } from '@cloud-ru/ds-utils';
if (isBrowser()) {
window.addEventListener('resize', handler);
}
Типы: ValueOf, WithSupportProps
import type { ValueOf, WithSupportProps } from '@cloud-ru/ds-utils';
const sizes = { sm: 'small', md: 'medium', lg: 'large' } as const;
type Size = ValueOf<typeof sizes>;
type ButtonProps = WithSupportProps<{
label: string;
variant: 'primary' | 'secondary';
}>;
Storybook
Интерактивные примеры — раздел Utils в локальном Storybook (pnpm dev:storybook).