8.x → 9.x 마이그레이션 가이드
8.x → 9.x 마이그레이션 가이드
8.x에서 9.x로 올리면서 필요한 사전 요구사항과 주요 변경 사항들을 다뤄요. React 버전, 의존성 갱신부터 use-form 타입, 훅 변경, 컴포넌트 prop 변경까지 하나씩 설명해 드릴게요.
출처: 문서
본문
LLM 에이전트로 마이그레이션하기
LLM 에이전트를 사용해 8.x에서 9.x로의 마이그레이션을 도울 수 있어요. LLM 문서에는 모든 breaking change와 마이그레이션 단계가 AI 코딩 도구에 최적화된 형식으로 포함되어 있어요. md 문서 파일 링크를 복사하고 AI 에이전트에게 마이그레이션을 요청하세요.
사전 요구사항
Mantine 9.x는 React 19.2 이상을 요구해요. 프로젝트가 더 오래된 React 버전을 사용한다면 Mantine 9.x로 마이그레이션하기 전에 React를 갱신해야 해요. 아직 React를 19.2+로 갱신할 수 없다면, React를 갱신하고 Mantine 9.x로 마이그레이션할 준비가 될 때까지 Mantine 8.x를 계속 사용할 수 있어요.
의존성 갱신
- 모든
@mantine/*패키지를 9.6.3 버전으로 갱신 @mantine/tiptap패키지를 사용한다면 모든@tiptap/*패키지를 최신3.x버전으로 갱신@mantine/charts패키지를 사용한다면recharts를 최신3.x버전으로 갱신
use-form TransformValues 타입
useForm 훅의 두 번째 제네릭 타입은 이제 transform 함수 타입이 아니라 변환된 값(transformed values)의 타입이에요. 새 사용 예시:
import { useForm } from '@mantine/form';
interface FormValues {
name: string;
locationId: string;
}
interface TransformedValues {
name: string;
locationId: number;
}
function Demo() {
const form = useForm<FormValues, TransformedValues>({
mode: 'uncontrolled',
initialValues: {
name: '',
locationId: '2',
},
transformValues: (values) => ({
...values,
locationId: Number(values.locationId),
}),
});
}
Text color prop
Text과 Anchor 컴포넌트의 color prop이 제거됐어요. 대신 c style prop을 사용하세요:
import { Text } from '@mantine/core';
// ❌ No longer works
function Demo() {
return <Text color="red">Text</Text>;
}
// ✅ Use the c style prop
function Demo() {
return <Text c="red">Text</Text>;
}
light variant 색상 변경
Mantine 9에서 light variant CSS 변수는 투명도 대신 solid 색상 값을 사용하도록 변경됐어요. 마이그레이션 중 8.x 동작을 유지해야 한다면 v8CssVariablesResolver를 사용하세요:
import {
Button,
MantineProvider,
v8CssVariablesResolver,
} from '@mantine/core';
function Demo() {
return (
<MantineProvider cssVariablesResolver={v8CssVariablesResolver}>
<Button variant="light" color="blue.6">
Uses 8.x light variant colors
</Button>
</MantineProvider>
);
}
Form resolvers
9.x에서 @mantine/form은 Standard Schema를 내장 지원해요. 스키마 라이브러리가 Standard Schema(Zod v4, Valibot, ArkType)를 지원한다면 전용 resolver 패키지 대신 내장 schemaResolver를 사용하세요.
8.x 예시:
import { z } from 'zod';
// ❌ No longer works; zodResolver is not exported from @mantine/form
import { useForm, zodResolver } from '@mantine/form';
const schema = z.object({
email: z.string().email({ message: 'Invalid email' }),
});
const form = useForm({
initialValues: { email: '' },
validate: zodResolver(schema),
});
Standard Schema를 사용한 9.x 예시 (권장):
import { z } from 'zod/v4';
import { useForm, schemaResolver } from '@mantine/form';
const schema = z.object({
email: z.email({ error: 'Invalid email' }),
});
const form = useForm({
initialValues: { email: '' },
validate: schemaResolver(schema, { sync: true }),
});
TypographyStylesProvider
- TypographyStylesProvider 컴포넌트가 Typography로 이름이 바뀌었어요:
// ❌ No longer works
import { TypographyStylesProvider } from '@mantine/core';
// ✅ Use the Typography component
import { Typography } from '@mantine/core';
Popover과 Tooltip positionDependencies prop
Popover와 Tooltip 컴포넌트는 더 이상 positionDependencies prop을 받지 않아요. 더 이상 필요하지 않고, 위치가 이제 자동으로 계산돼요:
import { Popover } from '@mantine/core';
// ❌ positionDependencies is no longer needed
function Demo(props) {
return (
<Popover position="top" positionDependencies={[props.a, props.b]}>
{/* ... */}
</Popover>
);
}
// ✅ The position is recalculated automatically
function Demo(props) {
return (
<Popover position="top">
{/* ... */}
</Popover>
);
}
use-fullscreen 훅 변경
use-fullscreen 훅이 useFullscreenElement와 useFullscreenDocument 두 훅으로 분리됐어요. 이 변경은 이전 구현의 stale ref 문제를 고치기 위해 필요했어요.
document 요소를 사용하는 새 사용법:
import { useFullscreenDocument } from '@mantine/hooks';
import { Button } from '@mantine/core';
function Demo() {
const { toggle, fullscreen } = useFullscreenDocument();
return (
<Button onClick={toggle} color={fullscreen ? 'red' : 'blue'}>
{fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'}
</Button>
);
}
커스텀 대상 요소를 사용하는 새 사용법:
import { useFullscreenElement } from '@mantine/hooks';
import { Button, Stack } from '@mantine/core';
function RefDemo() {
const { ref, toggle, fullscreen } = useFullscreenElement();
return (
<Stack align="center">
<img
ref={ref}
src="https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-4.png"
alt="For demo"
width={200}
/>
<Button onClick={toggle} color={fullscreen ? 'red' : 'blue'}>
{fullscreen ? 'Exit Fullscreen' : 'View Image Fullscreen'}
</Button>
</Stack>
);
}
use-mouse 훅 변경
use-mouse 훅이 useMouse와 useMousePosition 두 훅으로 분리됐어요. 이 변경은 이전 구현의 stale ref 문제를 고치기 위해 필요했어요.
document 요소를 사용하는 이전 사용법:
import { Text, Code } from '@mantine/core';
import { useMouse } from '@mantine/hooks';
function Demo() {
const { x, y } = useMouse();
return (
<Text ta="center">
Mouse coordinates <Code>{`{ x: ${x}, y: ${y} }`}</Code>
</Text>
);
}
document를 사용하는 새 사용법:
import { Text, Code } from '@mantine/core';
import { useMousePosition } from '@mantine/hooks';
function Demo() {
const { x, y } = useMousePosition();
return (
<Text ta="center">
Mouse coordinates <Code>{`{ x: ${x}, y: ${y} }`}</Code>
</Text>
);
}
use-mutation-observer 훅 변경
use-mutation-observer 훅이 이제 새로운 콜백 ref 방식(callback ref)을 사용해요. 이 변경은 stale ref 문제를 고치고 동적 노드 변경과의 호환성을 개선하기 위해 필요했어요.
이전 사용법 (8.x):
import { useMutationObserver } from '@mantine/hooks';
useMutationObserver(
(mutations) => console.log(mutations),
{ childList: true },
// ❌ The third argument is no longer supported; use `useMutationObserverTarget` instead
document.getElementById('external-element')
);
새 사용법 (9.x):
import { useMutationObserverTarget } from '@mantine/hooks';
// ✅ Rename the hook to `useMutationObserverTarget`
useMutationObserverTarget(
(mutations) => console.log(mutations),
{ childList: true },
// ✅ Pass the target element as the third argument
document.getElementById('external-element')
);
훅 타입 이름 변경
@mantine/hooks 타입이 일관성을 위해 이름이 바뀌었어요. 코드베이스에서 이름을 바꿔주세요:
UseScrollSpyReturnType→UseScrollSpyReturnValueStateHistory→UseStateHistoryValueOS→UseOSReturnValue
Collapse in → expanded
Collapse 컴포넌트가 이제 in 대신 expanded prop을 사용해요:
import { Collapse } from '@mantine/core';
// ❌ No longer works
function Demo() {
return (
<Collapse in={false}>
{/* ... */}
</Collapse>
);
}
// ✅ Use the expanded prop
function Demo() {
return (
<Collapse expanded={false}>
{/* ... */}
</Collapse>
);
}
Spoiler initialState → defaultExpanded
Spoiler 컴포넌트의 initialState prop이 다른 Mantine 컴포넌트와 일관성을 위해 defaultExpanded로 이름이 바뀌었어요:
import { Spoiler } from '@mantine/core';
// ❌ No longer works
function Demo() {
return (
<Spoiler initialState={false} showLabel="Show" hideLabel="Hide">
{/* ... */}
</Spoiler>
);
}
// ✅ Use the defaultExpanded prop
function Demo() {
return (
<Spoiler defaultExpanded={false} showLabel="Show" hideLabel="Hide">
{/* ... */}
</Spoiler>
);
}
Grid gutter → gap
Grid 컴포넌트의 gutter prop이 다른 레이아웃 컴포넌트(Flex, SimpleGrid 등)와 일관성을 위해 gap으로 이름이 바뀌었어요. 또한 세로/가로 간격을 각각 제어하는 새 rowGap과 columnGap prop이 추가됐어요:
import { Grid } from '@mantine/core';
// ❌ No longer works
function Demo() {
return (
<Grid gutter="xl">
<Grid.Col span={6}>1</Grid.Col>
<Grid.Col span={6}>2</Grid.Col>
</Grid>
);
}
// ✅ Use the gap prop
function Demo() {
return (
<Grid gap="xl">
<Grid.Col span={6}>1</Grid.Col>
<Grid.Col span={6}>2</Grid.Col>
</Grid>
);
}
// ✅ New: Separate row and column gaps
function Demo() {
return (
<Grid rowGap="xl" columnGap="sm">
<Grid.Col span={6}>1</Grid.Col>
<Grid.Col span={6}>2</Grid.Col>
</Grid>
);
}
Grid overflow="hidden" 더 이상 필요 없음
Grid 컴포넌트는 더 이상 열 사이 간격에 음수 마진을 사용하지 않아요. 이제 네이티브 CSS gap 속성을 사용하므로, Grid 컴포넌트에서 overflow="hidden"을 안전하게 제거할 수 있어요. 콘텐츠 오버플로 방지에 더 이상 필요하지 않아요:
import { Grid } from '@mantine/core';
// ❌ overflow="hidden" is no longer needed
function Demo() {
return (
<Grid overflow="hidden">
<Grid.Col span={6}>1</Grid.Col>
<Grid.Col span={6}>2</Grid.Col>
</Grid>
);
}
// ✅ Remove overflow="hidden"
function Demo() {
return (
<Grid>
<Grid.Col span={6}>1</Grid.Col>
<Grid.Col span={6}>2</Grid.Col>
</Grid>
);
}
useLocalStorage와 useSessionStorage 반환 타입
useLocalStorage와 useSessionStorage 훅은 이제 defaultValue를 제공하지 않을 때 반환 타입에 undefined를 올바르게 포함해요. 이전에는 defaultValue 없이 이 훅을 호출하면 런타임에서 값이 undefined일 수 있는데도 값 타입을 T(예: string)로 타이핑했어요.
잘못된 타입에 의존했다면 undefined를 처리하도록 코드를 갱신하세요:
import { useLocalStorage } from '@mantine/hooks';
// ❌ In 8.x, `value` was typed as `string` (incorrect)
const [value, setValue] = useLocalStorage({ key: 'my-key' });
// ✅ In 9.x, `value` is typed as `string | undefined`
const [value, setValue] = useLocalStorage({ key: 'my-key' });
// ✅ Provide defaultValue to keep the previous non-undefined type
const [value, setValue] = useLocalStorage({
key: 'my-key',
defaultValue: '',
});
같은 변경이 readLocalStorageValue, useSessionStorage, readSessionStorageValue에도 적용돼요.
useHeadroom이 객체를 반환
useHeadroom 훅이 이제 단순 boolean 대신 { pinned: boolean; scrollProgress: number } 객체를 반환해요. scrollProgress는 0(완전히 숨김)과 1(완전히 보임) 사이 값으로, 스크롤 연동 표시(reveal) 애니메이션에 사용할 수 있어요. 새 scrollDistance 옵션은 요소를 완전히 표시/숨기는 데 필요한 스크롤 픽셀 수를 제어해요(기본값: 100).
import { useHeadroom } from '@mantine/hooks';
// ❌ In 8.x, the return type is plain boolean
const pinned = useHeadroom({ fixedAt: 120 });
// ✅ In 9.x, the return type is an object containing `pinned` property
const { pinned } = useHeadroom({ fixedAt: 120 });
기본 border-radius 변경
8.x에서 기본 border-radius(theme.defaultRadius)는 sm(4px)이었어요. 9.x에서는 기본 border-radius가 md(8px)로 변경됐어요. 이전 동작을 유지하려면 테마에서 defaultRadius를 sm으로 설정하세요:
import { createTheme, MantineProvider } from '@mantine/core';
const theme = createTheme({
defaultRadius: 'sm',
});
function Demo() {
return <MantineProvider theme={theme}>{/* Your app */}</MantineProvider>;
}
Notifications pauseResetOnHover 기본값 변경
8.x에서 알림 위에 마우스를 올리면 해당 알림의 자동 닫힘 타이머만 일시 정지됐어요. 9.x에서는 기본 동작이 변경되어 어떤 알림에든 마우스를 올리면 모든 보이는 알림의 자동 닫힘 타이머가 일시 정지돼요. 이전 동작을 유지하려면 pauseResetOnHover="notification"으로 설정하세요:
import { Notifications } from '@mantine/notifications';
function Demo() {
return <Notifications pauseResetOnHover="notification" />;
}
더 알아보기 (Learn more)
- 7.x to 8.x migration — 7.x → 8.x 마이그레이션
- Migration guide Tiptap 2 → Tiptap 3 — Tiptap 2 → 3 마이그레이션 가이드