# Version v7.17.0

## Portal reuseTargetNode prop

[Portal](https://mantine.dev/llms/core-portal.md) component now supports `reuseTargetNode` prop which allows to reuse the same target node for all instances.
This option is more performant than the previous behavior, it is recommended to be enabled.
This option will be enabled by default in the `8.0` major release.

To enable reuseTargetNode option in all components that depend on Portal, add the following code
to your [theme](https://mantine.dev/llms/theming-theme-object.md):

```tsx
import { createTheme, Portal } from '@mantine/core';

export const theme = createTheme({
  components: {
    Portal: Portal.extend({
      defaultProps: {
        reuseTargetNode: true,
      },
    }),
  }
});
```

Example usage. In the following example, all three paragraphs will be rendered in the same target node:

```tsx
import { Portal } from '@mantine/core';

function Demo() {
  return (
    <>
      <Portal reuseTargetNode>
        <p>First</p>
      </Portal>

      <Portal reuseTargetNode>
        <p>Second</p>
      </Portal>

      <Portal reuseTargetNode>
        <p>Third</p>
      </Portal>
    </>
  );
}
```

## use-form formRootRule

`formRootRule` is a special rule path that can be used to [validate](https://mantine.dev/llms/form-validation.md) objects and arrays
alongside with their nested fields. For example, it is useful when you want to capture
a list of values, validate each value individually and then validate the list itself
to not be empty:

```tsx
import { TrashIcon } from '@phosphor-icons/react';
import { ActionIcon, Button, Group, Switch, Text, TextInput } from '@mantine/core';
import { formRootRule, isNotEmpty, useForm } from '@mantine/form';
import { randomId } from '@mantine/hooks';

function Demo() {
  const form = useForm({
    mode: 'uncontrolled',
    initialValues: {
      employees: [{ name: '', active: false, key: randomId() }],
    },
    validate: {
      employees: {
        [formRootRule]: isNotEmpty('AtIcon least one employee is required'),
        name: isNotEmpty('Name is required'),
      },
    },
  });

  const fields = form.getValues().employees.map((item, index) => (
    <Group key={item.key} mt="xs">
      <TextInput
        placeholder="John Doe"
        withAsterisk
        style={{ flex: 1 }}
        key={form.key(`employees.${index}.name`)}
        {...form.getInputProps(`employees.${index}.name`)}
      />
      <Switch
        label="Active"
        key={form.key(`employees.${index}.active`)}
        {...form.getInputProps(`employees.${index}.active`, { type: 'checkbox' })}
      />
      <ActionIcon color="red" onClick={() => form.removeListItem('employees', index)}>
        <TrashIcon size={16} />
      </ActionIcon>
    </Group>
  ));

  return (
    <form onSubmit={form.onSubmit(() => {})}>
      {fields.length > 0 ? (
        <Group mb="xs">
          <Text fw={500} size="sm" style={{ flex: 1 }}>
            Name
          </Text>
          <Text fw={500} size="sm" pr={90}>
            Status
          </Text>
        </Group>
      ) : (
        <Text c="dimmed" ta="center">
          No one here...
        </Text>
      )}

      {fields}

      {form.errors.employees && (
        <Text c="red" size="sm" mt="sm">
          {form.errors.employees}
        </Text>
      )}

      <Group justify="space-between" mt="md">
        <Button
          variant="default"
          onClick={() => {
            form.insertListItem('employees', { name: '', active: false, key: randomId() });
            form.clearFieldError('employees');
          }}
        >
          Add employee
        </Button>
        <Button type="submit">Submit</Button>
      </Group>
    </form>
  );
}
```


Another example is to validate an object fields combination:

```tsx
import { Button, Text, TextInput } from '@mantine/core';
import { formRootRule, isNotEmpty, useForm } from '@mantine/form';

function Demo() {
  const form = useForm({
    mode: 'uncontrolled',
    initialValues: {
      user: {
        firstName: '',
        lastName: '',
      },
    },

    validate: {
      user: {
        [formRootRule]: (value) =>
          value.firstName.trim().length > 0 && value.firstName === value.lastName
            ? 'First name and last name cannot be the same'
            : null,
        firstName: isNotEmpty('First name is required'),
        lastName: isNotEmpty('Last name is required'),
      },
    },
  });

  return (
    <form onSubmit={form.onSubmit(() => {})}>
      <TextInput
        label="First name"
        placeholder="First name"
        {...form.getInputProps('user.firstName')}
      />
      <TextInput
        label="Last name"
        placeholder="Last name"
        mt="md"
        {...form.getInputProps('user.lastName')}
      />
      {form.errors.user && (
        <Text c="red" mt={5} fz="sm">
          {form.errors.user}
        </Text>
      )}
      <Button type="submit" mt="lg">
        Submit
      </Button>
    </form>
  );
}
```


## isJSONString and isNotEmptyHTML form validators

New `isJSONString` and `isNotEmptyHTML` [form validators](https://mantine.dev/llms/form-validators.md):

* `isNotEmptyHTML` checks that form value is not an empty HTML string. Empty string, string with only HTML tags and whitespace are considered to be empty.
* `isJSONString` checks that form value is a valid JSON string.

```tsx
import { isJSONString, useForm } from '@mantine/form';

const form = useForm({
  mode: 'uncontrolled',
  initialValues: {
    json: '',
    html: '',
  },

  validate: {
    json: isJSONString('Invalid JSON string'),
    html: isNotEmptyHTML('HTML cannot be empty'),
  },
});
```

## Popover onDismiss

[Popover](https://mantine.dev/llms/core-popover.md) now supports `onDismiss` prop, which makes it easier
to subscribe to outside clicks and escape key presses to close popover:

```tsx
import { useState } from 'react';
import { Button, Popover } from '@mantine/core';

function Demo() {
  const [opened, setOpened] = useState(false);
  return (
    <Popover
      opened={opened}
      onDismiss={() => setOpened(false)}
    >
      <Popover.Target>
        <Button onClick={() => setOpened((o) => !o)}>
          Toggle popover
        </Button>
      </Popover.Target>

      <Popover.Dropdown>Dropdown</Popover.Dropdown>
    </Popover>
  );
}
```

## MantineProvider env

[MantineProvider](https://mantine.dev/llms/theming-mantine-provider.md) component now supports `env` prop.
It can be used in test environment to disable some features that
might impact tests and/or make it harder to test components:

* transitions that mount/unmount child component with delay
* portals that render child component in a different part of the DOM

To enable test environment, set `env` to `test`:

```tsx
import { MantineProvider } from '@mantine/core';

function Demo() {
  return (
    <MantineProvider env="test">
      {/* Your app here */}
    </MantineProvider>
  );
}
```

## use-file-dialog hook

New [use-file-dialog](https://mantine.dev/llms/hooks-use-file-dialog.md) allows capturing one or more files from the user
without file input element:

```tsx
import { Button, Group, List } from '@mantine/core';
import { useFileDialog } from '@mantine/hooks';

function Demo() {
  const fileDialog = useFileDialog();

  const pickedFiles = Array.from(fileDialog.files || []).map((file) => (
    <List.Item key={file.name}>{file.name}</List.Item>
  ));

  return (
    <div>
      <Group>
        <Button onClick={fileDialog.open}>Pick files</Button>
        {pickedFiles.length > 0 && (
          <Button variant="default" onClick={fileDialog.reset}>
            Reset
          </Button>
        )}
      </Group>
      {pickedFiles.length > 0 && <List mt="lg">{pickedFiles}</List>}
    </div>
  );
}
```


## Remix deprecation

[Remix](https://remix.run/) is deprecated, the documentation related to Remix integration
was removed, use [React Router](https://mantine.dev/llms/guides-react-router.md) instead. To simplify maintenance,
Remix/React Router templates were archived and will not be updated.

## Help center updates

* [I get hydration warning about data-mantine-color-scheme attribute, what does it mean?](https://help.mantine.dev/q/color-scheme-hydration-warning) question
* [How can I apply styles to all Mantine components?](https://help.mantine.dev/q/apply-styles-to-all) question

## Other changes

* [Tooltip](https://mantine.dev/llms/core-tooltip.md) now supports customizing `middlewares`
* [ScrollArea](https://mantine.dev/llms/core-scroll-area.md) now supports `overscrollBehavior` prop
* [Affix](https://mantine.dev/llms/core-affix.md) now supports `theme.spacing` values for `position` prop
* [Anchor](https://mantine.dev/llms/core-anchor.md) now supports `underline="not-hover"` option to display underline only when the link is not hovered
